diff --git a/.github/workflows/build-packages.yaml b/.github/workflows/build-packages.yaml new file mode 100644 index 00000000..e422a7bf --- /dev/null +++ b/.github/workflows/build-packages.yaml @@ -0,0 +1,60 @@ +name: Build packages + +on: + push: + branches: ['v2', 'master'] + +jobs: + build-packages: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install pkger + run: | + curl -L -o /tmp/pkger.deb https://github.com/vv9k/pkger/releases/download/0.11.0/pkger-0.11.0-0.amd64.deb + sudo apt -y install /tmp/pkger.deb + + - name: Build packages + run: pkger -c .pkger.yml build lact + + - name: Copy release files + run: | + OUT_DIR=$PWD/release-artifacts + mkdir -p $OUT_DIR + + pushd pkg/output + for DISTRO in $(ls); do + cd $DISTRO + rm -f *.src.rpm + + for FILE in $(ls); do + NAME="${FILE%.*}" + EXT="${FILE##*.}" + + OUT_NAME="$OUT_DIR/$NAME.$DISTRO.$EXT" + cp $FILE $OUT_NAME + done + cd .. + done + popd + + - name: Create release + uses: ncipollo/release-action@v1.12.0 + with: + removeArtifacts: true + allowUpdates: true + artifactErrorsFailBuild: true + artifacts: "release-artifacts/*" + body: ${{ github.event.head_commit.message }} + prerelease: true + name: Test release + tag: test-build + + - name: Update test-build tag + run: | + git tag -f test-build + git push -f origin test-build + shell: bash + diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f366bc34..09230880 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,22 +11,23 @@ env: jobs: build-test: - runs-on: ubuntu-20.04 + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v2 - name: Update repos run: sudo apt update - name: Install dependencies - run: sudo apt install libgtk-3-dev libvulkan-dev + run: sudo apt install libgtk-4-dev pkg-config libvulkan-dev - name: Build run: cargo build - name: Run tests - run: cargo test --verbose + run: cargo test --verbose --no-default-features check-format: - runs-on: ubuntu-20.04 - + runs-on: ubuntu-22.04 + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} steps: - uses: actions/checkout@v2 - name: install rustfmt diff --git a/.gitignore b/.gitignore index 40c3a7b2..caf39b11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,8 @@ /target +AppDir/ *.glade\~ +*.AppImage +*.AppImage.zsync +appimage-build/ +*.stignore +pkg/output diff --git a/.pkger.yml b/.pkger.yml new file mode 100644 index 00000000..04fcaced --- /dev/null +++ b/.pkger.yml @@ -0,0 +1,17 @@ +--- +recipes_dir: pkg/recipes +output_dir: pkg/output +images_dir: pkg/images +log_dir: ~ +runtime_uri: ~ +gpg_key: ~ +gpg_name: ~ +ssh: ~ +images: + - name: debian-12 + target: deb + - name: fedora-37 + target: rpm + - name: ubuntu-2204 + target: deb +custom_simple_images: ~ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index eb007c82..00000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'daemon'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=daemon" - ], - "filter": { - "name": "daemon", - "kind": "lib" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'daemon'", - "cargo": { - "args": [ - "build", - "--bin=daemon", - "--package=daemon" - ], - "filter": { - "name": "daemon", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'daemon'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=daemon", - "--package=daemon" - ], - "filter": { - "name": "daemon", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'cli'", - "cargo": { - "args": [ - "build", - "--bin=cli", - "--package=cli" - ], - "filter": { - "name": "cli", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'cli'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=cli", - "--package=cli" - ], - "filter": { - "name": "cli", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'gui'", - "cargo": { - "args": [ - "build", - "--bin=gui", - "--package=gui" - ], - "filter": { - "name": "gui", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'gui'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=gui", - "--package=gui" - ], - "filter": { - "name": "gui", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/API.md b/API.md new file mode 100644 index 00000000..69366ddf --- /dev/null +++ b/API.md @@ -0,0 +1,42 @@ +# Description + +The LACT Daemon exposes a JSON API over a unix socket, available on `/var/run/lactd.sock`. You can configure who has access to the socket in `/etc/lact/config.yaml` in the `daemon.admin_groups` field. + +The API expects newline-separated JSON objects, and returns a JSON object for every request. + +The general format of requests looks like: +``` +{"command": "command_name", "args": {}} +``` +Note that the type of `args` depends on the specific request, and may be ommited in some cases. + +The response looks like this: +``` +{"status": "ok|error", "data": {}} +``` +Same as `args` in requests, `data` can be of a different type and may not be present depending on the specific request. + +You can try sending commands to socket interactively with `ncat`: +``` +echo '{"command": "list_devices"}' | ncat -U /run/lactd.sock +``` +Example response: +``` +{"status":"ok","data":[{"id":"1002:687F-1043:0555-0000:0b:00.0","name":"Vega 10 XL/XT [Radeon RX Vega 56/64]"}]} +``` + +# Commands + +For the full list of available commands and responses, you can look at the source code of the schema: [requests](lact-schema/src/request.rs), [the basic response structure](lact-schema/src/response.rs) and [all possible types](lact-schema/src/lib.rs). + +It should also be fairly easy to figure out the API by trial and error, as the error message are quite verbose: + +``` +echo '{"command": "test"}' | ncat -U /run/lactd.sock + +{"status":"error","data":"Failed to deserialize request: unknown variant `test`, expected one of `ping`, `list_devices`, `system_info`, `device_info`, `device_stats`, `device_clocks_info`, `set_fan_control`, `set_power_cap`, `set_performance_level`, `set_clocks_value` at line 1 column 18"} +``` + +# Rust + +If you want to connect to the socket from a Rust program, you can simply import either the `lact-client` or `lact-schema` (if you want to write a custom client) crates from this repository. \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9b19b9c9..7e5c75de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,91 +3,57 @@ version = 3 [[package]] -name = "adler" -version = "1.0.2" +name = "ahash" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", +] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "amdgpu-sysfs" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "182b6991fe3b8ac1e0fc4ab6eed0e79740cb6363877283c5a42f02e164737c92" dependencies = [ - "winapi", + "enum_dispatch", + "serde", ] [[package]] name = "anyhow" -version = "1.0.62" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "ash" -version = "0.33.3+1.2.191" +version = "0.37.2+1.3.238" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219" +checksum = "28bf19c1f0a470be5fbf7522a308a05df06610252c5bcf5143e1b23f629a9a03" dependencies = [ "libloading", ] -[[package]] -name = "atk" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a83b21d2aa75e464db56225e1bda2dd5993311ba1095acaa8fa03d1ae67026ba" -dependencies = [ - "atk-sys", - "bitflags", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badcf670157c84bb8b1cf6b5f70b650fed78da2033c9eed84c4e49b11cbe83ea" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - [[package]] name = "bincode" version = "1.3.3" @@ -104,35 +70,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "bumpalo" -version = "3.12.0" +name = "bytemuck" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[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 = "cairo-rs" -version = "0.14.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b5725979db0c586d98abad2193cdb612dd40ef95cd26bd99851bf93b3cb482" +checksum = "a8af54f5d48af1226928adc1f57edd22f5df1349e7da1fc96ae15cf43db0e871" dependencies = [ "bitflags", "cairo-sys-rs", "glib", "libc", + "once_cell", "thiserror", ] [[package]] name = "cairo-sys-rs" -version = "0.14.9" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b448b876970834fda82ba3aeaccadbd760206b75388fc5c1b02f1e343b697570" +checksum = "f55382a01d30e5e53f185eee269124f5e21ab526595b872751278dfbb463594e" dependencies = [ "glib-sys", "libc", @@ -141,15 +122,15 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-expr" -version = "0.8.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b412e83326147c2bb881f8b40edfbf9905b9b8abaebd0e47ca190ba62fda8f0e" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" dependencies = [ "smallvec", ] @@ -160,47 +141,41 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - [[package]] name = "clap" -version = "2.34.0" +version = "4.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" dependencies = [ - "ansi_term", - "atty", "bitflags", + "clap_derive", + "clap_lex", + "is-terminal", + "once_cell", "strsim", - "textwrap", - "unicode-width", - "vec_map", + "termcolor", ] [[package]] -name = "cli" -version = "0.1.0" +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" dependencies = [ - "colored", - "daemon", - "env_logger 0.8.4", - "log", - "structopt", + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "colored" -version = "2.0.0" +name = "clap_lex" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" dependencies = [ - "atty", - "lazy_static", - "winapi", + "os_str_bytes", ] [[package]] @@ -220,19 +195,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] -name = "crc32fast" -version = "1.3.2" +name = "core-graphics-types" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ - "cfg-if", + "bitflags", + "core-foundation", + "foreign-types", + "libc", ] [[package]] name = "crossbeam-queue" -version = "0.3.6" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd42583b04998a5363558e5f9291ee5a5ff6b49944332103f251e7479a82aa7" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" dependencies = [ "cfg-if", "crossbeam-utils", @@ -240,99 +218,111 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if", - "once_cell", ] [[package]] -name = "daemon" -version = "0.1.0" +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ - "bincode", - "env_logger 0.9.0", - "log", - "nix", - "pciid-parser", - "rand", - "reqwest", - "serde", - "serde_json", - "signal-hook", - "vulkano", + "quote", + "syn", ] [[package]] -name = "either" -version = "1.8.0" +name = "darling" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "c0808e1bd8671fb44a113a14e13497557533369847788fa2ae912b6ebfce9fa8" +dependencies = [ + "darling_core", + "darling_macro", +] [[package]] -name = "encoding_rs" -version = "0.8.31" +name = "darling_core" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "001d80444f28e193f30c2f293455da62dcf9a6b29918a4253152ae2b1de592cb" dependencies = [ - "cfg-if", + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", ] [[package]] -name = "env_logger" -version = "0.8.4" +name = "darling_macro" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", + "darling_core", + "quote", + "syn", ] [[package]] -name = "env_logger" -version = "0.9.0" +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "enum_dispatch" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +checksum = "11f36e95862220b211a6e2aa5eca09b4fa391b13cd52ceb8035a24bf65a79de2" dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", + "once_cell", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ - "instant", + "errno-dragonfly", + "libc", + "winapi", ] [[package]] -name = "field-offset" -version = "0.3.4" +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "memoffset", - "rustc_version", + "cc", + "libc", ] [[package]] -name = "flate2" -version = "1.0.24" +name = "field-offset" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" dependencies = [ - "crc32fast", - "miniz_oxide", + "memoffset 0.6.5", + "rustc_version", ] [[package]] @@ -357,35 +347,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] -name = "form_urlencoded" -version = "1.0.1" +name = "futures" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ - "matches", - "percent-encoding", + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -394,30 +389,44 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", + "futures-sink", "futures-task", "memchr", "pin-project-lite", @@ -425,39 +434,25 @@ dependencies = [ "slab", ] -[[package]] -name = "gdk" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d749dcfc00d8de0d7c3a289e04a04293eb5ba3d8a4e64d64911d481fa9933b" -dependencies = [ - "bitflags", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - [[package]] name = "gdk-pixbuf" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534192cb8f01daeb8fab2c8d4baa8f9aae5b7a39130525779f5c2608e235b10f" +checksum = "b023fbe0c6b407bd3d9805d107d9800da3829dc5a676653210f1d5f16d7f59bf" dependencies = [ + "bitflags", "gdk-pixbuf-sys", "gio", "glib", "libc", + "once_cell", ] [[package]] name = "gdk-pixbuf-sys" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f097c0704201fbc8f69c1762dc58c6947c8bb188b8ed0bc7e65259f1894fe590" +checksum = "7b41bd2b44ed49d99277d3925652a163038bd5ed943ec9809338ffb2f4391e3b" dependencies = [ "gio-sys", "glib-sys", @@ -467,10 +462,26 @@ dependencies = [ ] [[package]] -name = "gdk-sys" -version = "0.14.0" +name = "gdk4" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5042053ee765aeef08d9d7e3f0f1e36a4d37f1659b3f93ad3d6997515dbb64a" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk-pixbuf", + "gdk4-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk4-sys" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e091b3d3d6696949ac3b3fb3c62090e5bfd7bd6850bef5c3c5ea701de1b1f1e" +checksum = "14f0fb00507af1e9299681dd09965f720e2b5ea95536d49a5681e8994ef10c7a" dependencies = [ "cairo-sys-rs", "gdk-pixbuf-sys", @@ -485,9 +496,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", @@ -496,26 +507,29 @@ dependencies = [ [[package]] name = "gio" -version = "0.14.8" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711c3632b3ebd095578a9c091418d10fed492da9443f58ebc8f45efbeb215cb0" +checksum = "65acfc24267314eee46f49e0a531e08fd6c3025040d1cfb4a7cd8e41c5e06116" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-io", + "futures-util", "gio-sys", "glib", "libc", "once_cell", + "pin-project-lite", + "smallvec", "thiserror", ] [[package]] name = "gio-sys" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a41df66e57fcc287c4bcf74fc26b884f31901ea9792ec75607289b456f48fa" +checksum = "b5d3076ecb86c8c3a672c9843d6232b3a344fb81d304d0ba1ac64b23343efa46" dependencies = [ "glib-sys", "gobject-sys", @@ -526,28 +540,32 @@ dependencies = [ [[package]] name = "glib" -version = "0.14.8" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c515f1e62bf151ef6635f528d05b02c11506de986e43b34a5c920ef0b3796a4" +checksum = "a78b6a0901e258cb03c761ca94c84d519427ede489cae12cd5ba0d7d584e69e9" dependencies = [ "bitflags", "futures-channel", "futures-core", "futures-executor", "futures-task", + "futures-util", + "gio-sys", "glib-macros", "glib-sys", "gobject-sys", "libc", + "memchr", "once_cell", "smallvec", + "thiserror", ] [[package]] name = "glib-macros" -version = "0.14.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518" +checksum = "55e93d79ed130f0f0b58bc0aa29fb0e40c9dfd63997fec51f8adf780d1520bc4" dependencies = [ "anyhow", "heck", @@ -560,9 +578,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.14.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c1d60554a212445e2a858e42a0e48cece1bd57b311a19a9468f70376cf554ae" +checksum = "72a0985cf568e18cf63b443c9a14f4bdaa947fed7437476000dba84926a20b25" dependencies = [ "libc", "system-deps", @@ -570,9 +588,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa92cae29759dae34ab5921d73fff5ad54b3d794ab842c117e36cafc7994c3f5" +checksum = "9a0155d388840c77d61b033b66ef4f9bc7f4133d83df83572d6b4fb234a3be7d" dependencies = [ "glib-sys", "libc", @@ -580,54 +598,90 @@ dependencies = [ ] [[package]] -name = "gtk" -version = "0.14.3" +name = "graphene-rs" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cf11565bb0e4dfc2f99d4775b6c329f0d40a2cff9c0066214d31a0e1b46256" +dependencies = [ + "glib", + "graphene-sys", + "libc", +] + +[[package]] +name = "graphene-sys" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf80a4849a8d9565410a8fec6fc3678e9c617f4ac7be182ca55ab75016e07af9" +dependencies = [ + "glib-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gsk4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb51122dd3317e9327ec1e4faa151d1fa0d95664cd8fb8dcfacf4d4d29ac70c" +checksum = "2fa9cd285a72a95124b65c069a9cb1b8fb8e310be71783404c39fccf3bf7774c" dependencies = [ - "atk", "bitflags", "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", + "gdk4", "glib", - "gtk-sys", - "gtk3-macros", + "graphene-rs", + "gsk4-sys", "libc", - "once_cell", "pango", - "pkg-config", ] [[package]] -name = "gtk-sys" -version = "0.14.0" +name = "gsk4-sys" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c14c8d3da0545785a7c5a120345b3abb534010fb8ae0f2ef3f47c027fba303e" +checksum = "5a445ae1e50cbf181a1d5c61b920a7e7e8657b96e0ecdbbf8911a86fad462a32" dependencies = [ - "atk-sys", "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", + "gdk4-sys", "glib-sys", "gobject-sys", + "graphene-sys", "libc", "pango-sys", "system-deps", ] [[package]] -name = "gtk3-macros" -version = "0.14.0" +name = "gtk4" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47dca53cb1a8ae3006e869b5711ae7370180db537f6d98e3bcaf23fabfd911f" +dependencies = [ + "bitflags", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "graphene-rs", + "gsk4", + "gtk4-macros", + "gtk4-sys", + "libc", + "once_cell", + "pango", +] + +[[package]] +name = "gtk4-macros" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79" +checksum = "db4676c4f90d8b010e88cb4558f61f47d76d6f6b8e6f6b89e62640f443907f61" dependencies = [ "anyhow", - "heck", "proc-macro-crate", "proc-macro-error", "proc-macro2", @@ -636,42 +690,33 @@ dependencies = [ ] [[package]] -name = "gui" -version = "0.1.0" +name = "gtk4-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65463dc801460e498d5e7ffa6e9ae2cfbed7d05fabd1ca5a8d024adbc89eeda6" dependencies = [ - "daemon", - "env_logger 0.9.0", - "glib", - "gtk", - "log", - "pango", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "graphene-sys", + "gsk4-sys", + "libc", + "pango-sys", + "system-deps", ] [[package]] -name = "h2" -version = "0.3.14" +name = "half" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "crunchy", ] -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - [[package]] name = "hashbrown" version = "0.12.3" @@ -680,157 +725,136 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" -version = "0.3.3" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" [[package]] -name = "http" -version = "0.2.8" +name = "ident_case" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] -name = "http-body" -version = "0.4.5" +name = "indexmap" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ - "bytes", - "http", - "pin-project-lite", + "autocfg", + "hashbrown", + "serde", ] [[package]] -name = "httparse" -version = "1.7.1" +name = "io-lifetimes" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "0.14.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3" dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", + "libc", + "windows-sys 0.45.0", ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "is-terminal" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857" dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys 0.45.0", ] [[package]] -name = "idna" -version = "0.2.3" +name = "itoa" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] -name = "indexmap" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +name = "lact" +version = "0.2.0" dependencies = [ - "autocfg", - "hashbrown", + "anyhow", + "clap", + "lact-cli", + "lact-daemon", + "lact-gui", ] [[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +name = "lact-cli" +version = "0.2.0" dependencies = [ - "cfg-if", + "anyhow", + "clap", + "lact-client", ] [[package]] -name = "ipnet" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +name = "lact-client" +version = "0.2.0" +dependencies = [ + "anyhow", + "lact-schema", + "nix", + "serde", + "serde_json", + "tracing", +] [[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +name = "lact-daemon" +version = "0.2.0" dependencies = [ - "either", + "amdgpu-sysfs", + "anyhow", + "bincode", + "futures", + "lact-schema", + "nix", + "pciid-parser", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "tokio", + "tracing", + "tracing-subscriber", + "vulkano", ] [[package]] -name = "itoa" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +name = "lact-gui" +version = "0.2.0" +dependencies = [ + "anyhow", + "gtk4", + "lact-client", + "lact-daemon", + "once_cell", + "pretty_assertions", + "tracing", + "tracing-subscriber", +] [[package]] -name = "js-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +name = "lact-schema" +version = "0.2.0" dependencies = [ - "wasm-bindgen", + "amdgpu-sysfs", + "indexmap", + "serde", + "serde_json", ] [[package]] @@ -841,25 +865,31 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -875,10 +905,22 @@ dependencies = [ ] [[package]] -name = "matches" -version = "0.1.9" +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] [[package]] name = "memchr" @@ -896,131 +938,103 @@ dependencies = [ ] [[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "miniz_oxide" -version = "0.5.3" +name = "memoffset" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ - "adler", + "autocfg", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi", - "windows-sys 0.36.1", -] - -[[package]] -name = "native-tls" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "windows-sys 0.45.0", ] [[package]] name = "nix" -version = "0.23.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", - "cc", "cfg-if", "libc", - "memoffset", + "memoffset 0.7.1", + "pin-utils", + "static_assertions", ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "nom8" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8" dependencies = [ - "hermit-abi", - "libc", + "memchr", ] [[package]] -name = "once_cell" -version = "1.13.1" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] [[package]] -name = "openssl" -version = "0.10.41" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", + "malloc_buf", ] [[package]] -name = "openssl-macros" -version = "0.1.0" +name = "once_cell" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] -name = "openssl-probe" -version = "0.1.5" +name = "os_str_bytes" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] -name = "openssl-sys" -version = "0.9.75" +name = "output_vt100" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", + "winapi", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "pango" -version = "0.14.8" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "546fd59801e5ca735af82839007edd226fe7d3bb06433ec48072be4439c28581" +checksum = "243c048be90312220fb3bd578176eed8290568274a93c95040289d39349384bc" dependencies = [ "bitflags", + "gio", "glib", "libc", "once_cell", @@ -1029,9 +1043,9 @@ dependencies = [ [[package]] name = "pango-sys" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2367099ca5e761546ba1d501955079f097caa186bb53ce0f718dca99ac1942fe" +checksum = "4293d0f0b5525eb5c24734d30b0ed02cd02aa734f216883f376b54de49625de8" dependencies = [ "glib-sys", "gobject-sys", @@ -1041,50 +1055,42 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-sys 0.45.0", ] [[package]] name = "pciid-parser" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5057bd953dfcccbaba6326a1dc2a6f0e7ca1d7b8ceca37bee610b639ef9f4a0" +checksum = "6e2ef5429455ae06f0c055262d540bf2c190ad51b72bb6cbe8488706c4dff440" dependencies = [ + "serde", "tracing", - "ureq", ] -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - [[package]] name = "pest" -version = "2.3.0" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b0560d531d1febc25a3c9398a62a71256c0178f2e3443baedd9ad4bb8c9deb4" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" dependencies = [ "thiserror", "ucd-trie", @@ -1104,25 +1110,30 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] -name = "ppv-lite86" -version = "0.2.16" +name = "pretty_assertions" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit", ] [[package]] @@ -1151,52 +1162,22 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -1208,9 +1189,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -1218,71 +1199,19 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[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 = "reqwest" -version = "0.11.11" +name = "regex-automata" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "base64", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-tls", - "ipnet", - "js-sys", - "lazy_static", - "log", - "mime", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", + "regex-syntax", ] [[package]] -name = "ring" -version = "0.16.20" +name = "regex-syntax" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "rustc_version" @@ -1294,32 +1223,24 @@ dependencies = [ ] [[package]] -name = "rustls" -version = "0.20.6" +name = "rustix" +version = "0.36.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644" dependencies = [ - "log", - "ring", - "sct", - "webpki", + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", ] [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "schannel" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" -dependencies = [ - "lazy_static", - "windows-sys 0.36.1", -] +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "scopeguard" @@ -1327,39 +1248,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "security-framework" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "0.11.0" @@ -1380,18 +1268,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -1400,9 +1288,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" dependencies = [ "itoa", "ryu", @@ -1410,130 +1298,100 @@ dependencies = [ ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "serde_with" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "30d904179146de381af4c93d3af6ca4984b3152db687dacb9c3c35e86f39809c" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", "serde", + "serde_with_macros", ] [[package]] -name = "shared_library" -version = "0.1.9" +name = "serde_with_macros" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" +checksum = "a1966009f3c05f095697c537312f5415d1e3ed31ce0a56942bac4c771c5c335e" dependencies = [ - "lazy_static", - "libc", + "darling", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "signal-hook" -version = "0.3.14" +name = "serde_yaml" +version = "0.9.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567" dependencies = [ - "libc", - "signal-hook-registry", + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", ] [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", ] [[package]] -name = "spin" -version = "0.5.2" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.26" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "strum" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf86bbcfd1fa9670b7a129f64fc0c9fcbbfe4f1bc4210e9e98fe71ffc12cde2" - -[[package]] -name = "strum_macros" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -1542,68 +1400,40 @@ dependencies = [ [[package]] name = "system-deps" -version = "3.2.0" +version = "6.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "480c269f870722b3b08d2f13053ce0c2ab722839f472863c3e2d61ff3a1c2fa6" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" dependencies = [ - "anyhow", "cfg-expr", "heck", - "itertools", "pkg-config", - "strum", - "strum_macros", - "thiserror", "toml", "version-compare", ] -[[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 = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1611,20 +1441,15 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "thread_local" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ - "tinyvec_macros", + "cfg-if", + "once_cell", ] -[[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.25.0" @@ -1636,56 +1461,55 @@ dependencies = [ "libc", "memchr", "mio", - "num_cpus", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.42.0", ] [[package]] -name = "tokio-native-tls" -version = "0.3.0" +name = "tokio-macros" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ - "native-tls", - "tokio", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "tokio-util" -version = "0.7.3" +name = "toml" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", + "serde", ] [[package]] -name = "toml" -version = "0.5.9" +name = "toml_datetime" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] +checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5" [[package]] -name = "tower-service" -version = "0.3.2" +name = "toml_edit" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b" +dependencies = [ + "indexmap", + "nom8", + "toml_datetime", +] [[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", "pin-project-lite", @@ -1695,9 +1519,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", @@ -1706,110 +1530,72 @@ 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", + "valuable", ] [[package]] -name = "try-lock" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" - -[[package]] -name = "ucd-trie" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" - -[[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.3" +name = "tracing-log" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" - -[[package]] -name = "unicode-normalization" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "tinyvec", + "lazy_static", + "log", + "tracing-core", ] [[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "ureq" -version = "2.5.0" +name = "tracing-subscriber" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97acb4c28a254fd7a4aeec976c46a7fa404eac4d7c134b30c75144846d7cb8f" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" dependencies = [ - "base64", - "chunked_transfer", - "flate2", - "log", + "matchers", + "nu-ansi-term", "once_cell", - "rustls", - "url", - "webpki", - "webpki-roots", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] -name = "url" -version = "2.2.2" +name = "ucd-trie" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] -name = "vcpkg" -version = "0.2.15" +name = "unsafe-libyaml" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" [[package]] -name = "vec_map" -version = "0.8.2" +name = "valuable" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "version-compare" -version = "0.0.11" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" [[package]] name = "version_check" @@ -1819,147 +1605,57 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vk-parse" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1538fa783f47fb5a6eb495f024f426599a4fe66ff3773ff2252156c64d350a" +checksum = "4c6a0bda9bbe6b9e50e6456c80aa8fe4cca3b21e4311a1130c41e4915ec2e32a" dependencies = [ "xml-rs", ] [[package]] name = "vulkano" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0c9cc7471d065c6066b2a0daaae4232a100fdb6f0aad2a4b12652a2bd8ede0" +version = "0.32.0" +source = "git+https://github.com/vulkano-rs/vulkano#6994ce613e2e23ae216ec2c7956dff2f8f0ee208" dependencies = [ + "ahash", "ash", + "bytemuck", + "core-graphics-types", "crossbeam-queue", - "fnv", "half", "heck", "indexmap", - "lazy_static", + "libloading", + "objc", + "once_cell", "parking_lot", "proc-macro2", "quote", "regex", "serde", "serde_json", - "shared_library", "smallvec", + "thread_local", "vk-parse", + "vulkano-macros", ] [[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 = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +name = "vulkano-macros" +version = "0.32.0" +source = "git+https://github.com/vulkano-rs/vulkano#6994ce613e2e23ae216ec2c7956dff2f8f0ee208" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "web-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring", - "untrusted", ] [[package]] -name = "webpki-roots" -version = "0.22.4" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" -dependencies = [ - "webpki", -] +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" @@ -1994,30 +1690,41 @@ 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_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "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-sys" -version = "0.42.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", + "windows_x86_64_msvc", ] [[package]] @@ -2026,48 +1733,24 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - [[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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - [[package]] name = "windows_x86_64_gnu" version = "0.42.1" @@ -2080,29 +1763,20 @@ 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" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - [[package]] name = "windows_x86_64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" -dependencies = [ - "winapi", -] - [[package]] name = "xml-rs" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/Cargo.toml b/Cargo.toml index 83b3b4c2..75cf7cf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["daemon", "cli", "gui"] \ No newline at end of file +resolver = "2" +members = ["lact*"] diff --git a/LICENSE b/LICENSE index c566f68c..9ae4d0b3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 ilyazzz +Copyright (c) 2023 Ilya Zlobintsev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..8ed10dbf --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +export CARGO_TARGET_DIR ?= ./target +DESTDIR ?= /usr/local + +build-release: + cargo build --release + +install: + install -Dm755 target/release/lact ${DESTDIR}/bin/lact + install -Dm755 res/lactd.service ${DESTDIR}/lib/systemd/system/lactd.service + install -Dm755 res/io.github.lact-linux.desktop ${DESTDIR}/share/applications/io.github.lact-linux.desktop + install -Dm755 res/io.github.lact-linux.png ${DESTDIR}/share/pixmaps/io.github.lact-linux.png + +uninstall: + rm ${DESTDIR}/bin/lact + rm ${DESTDIR}/lib/systemd/system/lactd.service + rm ${DESTDIR}/share/applications/io.github.lact-linux.desktop + rm ${DESTDIR}/share/pixmaps/io.github.lact-linux.png diff --git a/README.md b/README.md index a96f321b..263c8011 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,12 @@ # Linux AMDGPU Control Application +icon + This application allows you to control your AMD GPU on a Linux system. -| | | | +| GPU info | Overclocking | Fan control | |----------------------------------------------|----------------------------------------------|---------------------------------------------| -|![Screenshot](https://i.imgur.com/crEN4az.png)|![Screenshot](https://i.imgur.com/x7fTKpT.png)|![Screenshot](https://i.imgur.com/idAER4B.png) - +|![image](https://user-images.githubusercontent.com/22796665/221357224-21163f94-1afd-4bb8-96bd-24f9ce1816ce.png)|![image](https://user-images.githubusercontent.com/22796665/221357297-9c03bcb5-0742-459b-bffb-9e75c93df25b.png)|![image](https://user-images.githubusercontent.com/22796665/221357332-6d26a65f-d522-4b04-86a8-c9820b334416.png) Current features: @@ -15,25 +16,37 @@ Current features: - Basic overclocking Currently missing: -- Voltage control on Vega20+ GPUs - Precise clock/voltage curve manipulation (currently can only set the maximum values) -- Multi-GPU system support *Should work now* # Installation - Arch Linux: Install the [AUR Package](https://aur.archlinux.org/packages/lact/) (or the -git version) -- Debian/Ubuntu/Pop_OS: Download a .deb from [releases](https://github.com/ilyazzz/LACT/releases/). Warning: it has not been tested heavily -- Otherwise, build from source: +- Debian/Ubuntu/Derevatives: Download a .deb from [releases](https://github.com/ilya-zlobintsev/LACT/releases/). + + It is only available on Debian 12+ and Ubuntu 22.04+ as older versions don't ship gtk4. +- Fedora: an rpm is available in [releases](https://github.com/ilya-zlobintsev/LACT/releases/). +- Otherwise, build from source. + +**Why is there no AppImage/Flatpak/other universal format?** +See [here](./pkg/README.md). +# Configuration + +There is a configuration file available in `/etc/lact/config.yaml`. Most of the settings are accessible through the GUI, but some of them may be useful to be edited manually (like `admin_groups` to specify who has access to the daemon) # Building from source -- Install dependencies: - - Ubuntu/Debian: `sudo apt install cargo rustc libvulkan-dev git libgtk-3-dev make` - - Fedora: `sudo dnf install git gtk3-devel rust cargo vulkan-headers perl-core` -- `git clone https://github.com/ilyazzz/LACT && cd LACT` -- `./deploy.sh` +Dependencies: +- rust +- gtk4 +- pkg-config +- make +- hwdata +Steps: +- `git clone https://github.com/ilya-zlobintsev/LACT && cd LACT` +- `make` +- `sudo make install` # Usage @@ -41,65 +54,48 @@ Enable and start the service (otherwise you won't be able to change any settings ``` sudo systemctl enable --now lactd ``` -You can now use the application. +You can now use the GUI to change settings and view information. + +# API +There is an API available over a unix socket. See [here](API.md) for more information. # CLI There is also a cli available. -- Getting basic information: +- List system GPUs: - `lact-cli info` + `lact cli list-gpus` Example output: ``` - GPU Model: Radeon RX 570 Pulse 4GB - GPU Vendor: Advanced Micro Devices, Inc. [AMD/ATI] - Driver in use: amdgpu - VBIOS Version: 113-1E3871U-O4C - VRAM Size: 4096 - Link Speed: 8.0 GT/s PCIe + 1002:687F-1043:0555-0000:0b:00.0 (Vega 10 XL/XT [Radeon RX Vega 56/64]) ``` -- Getting current GPU stats: +- Getting GPU information: - `lact-cli metrics` + `lact cli info` Example output: ``` - VRAM Usage: 545/4096MiB - Temperature: 46°C - Fan Speed: 785/3200RPM - GPU Clock: 783MHz - GPU Voltage: 0.975V - VRAM Clock: 1750MHz - Power Usage: 38/155W + lact cli info + GPU Vendor: Advanced Micro Devices, Inc. [AMD/ATI] + GPU Model: Vega 10 XL/XT [Radeon RX Vega 56/64] + Driver in use: amdgpu + VBIOS version: 115-D050PIL-100 + Link: LinkInfo { current_width: Some("16"), current_speed: Some("8.0 GT/s PCIe"), max_width: Some("16"), max_speed: Some("8.0 GT/s PCIe") } ``` -- Showing the current fan curve: - - `lact-cli curve status` - - Example output: - - ``` - Fan curve: - 20C°: 0% - 40C°: 0% - 60C°: 50% - 80C°: 88% - 100C°: 100% - ``` +The functionality of the CLI is quite limited. If you want to integrate LACT with some application/script, you should use the [API](API.md) instead. # Reporting issues - When reporting issues, please include your system info and GPU model. +When reporting issues, please include your system info and GPU model. - If there's a crash, run `lact-gui` from the command line to get logs, or use `journalctl -u lactd` to see if the daemon crashed. +If there's a crash, run `lact gui` from the command line to get logs, or use `journalctl -u lactd` to see if the daemon crashed. - If there's an issue with GPU model identification please report it [here](https://github.com/ilyazzz/pci-id-parser/), include your GPU model and the output of `cat /sys/class/drm/card*/device/uevent`. # Alternatives -If LACT doesn't end up working for you, make sure to check out [CoreCtrl](https://gitlab.com/corectrl/corectrl). +If LACT doesn't do what you want, make sure to check out [CoreCtrl](https://gitlab.com/corectrl/corectrl). diff --git a/cli/Cargo.toml b/cli/Cargo.toml deleted file mode 100644 index 541e0448..00000000 --- a/cli/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "cli" -version = "0.1.0" -authors = ["Ilya Zlobintsev "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -daemon = { path = "../daemon" } -structopt = "0.3" -log = "0.4" -env_logger = "0.8" -colored = "2" \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs deleted file mode 100644 index 1c1ffd76..00000000 --- a/cli/src/main.rs +++ /dev/null @@ -1,236 +0,0 @@ -use colored::*; -use daemon::daemon_connection::DaemonConnection; -use structopt::StructOpt; - -#[derive(StructOpt)] -enum ConfigOpt { - Show, - AllowOnlineUpdating, - DisallowOnlineUpdating, -} - -#[derive(StructOpt)] -enum CurveOpt { - /// Shows current fan control information - Status { - /// Specify a GPU ID as printed in `lact-cli gpus`. By default, all GPUs are printed. - gpu_id: Option, - }, -} - -#[derive(StructOpt)] -#[structopt(rename_all = "lower")] -enum Opt { - /// Realtime GPU information - Metrics { - /// Specify a GPU ID as printed in `lact-cli gpus`. By default, all GPUs are printed. - gpu_id: Option, - }, - /// Get GPU list - Gpus, - /// General information about the GPU - Info { - /// Specify a GPU ID as printed in `lact-cli gpus`. By default, all GPUs are printed. - gpu_id: Option, - }, - Config(ConfigOpt), - /// Fan curve control - Curve(CurveOpt), -} - -fn main() { - env_logger::init(); - - let opt = Opt::from_args(); - - let d = DaemonConnection::new().unwrap(); - log::trace!("connection established"); - - match opt { - Opt::Gpus => { - let gpus = d.get_gpus(); - println!("{:?}", gpus); - } - Opt::Metrics { gpu_id } => { - let mut gpu_ids: Vec = Vec::new(); - - if let Some(gpu_id) = gpu_id { - gpu_ids.push(gpu_id); - } else { - for (gpu_id, _) in d.get_gpus().unwrap() { - gpu_ids.push(gpu_id); - } - } - - for gpu_id in gpu_ids { - print_stats(&d, gpu_id); - } - } - Opt::Info { gpu_id } => { - let mut gpu_ids: Vec = Vec::new(); - - if let Some(gpu_id) = gpu_id { - gpu_ids.push(gpu_id); - } else { - for (gpu_id, _) in d.get_gpus().unwrap() { - gpu_ids.push(gpu_id); - } - } - - for gpu_id in gpu_ids { - print_info(&d, gpu_id); - } - } - Opt::Curve(curve) => match curve { - CurveOpt::Status { gpu_id } => { - let mut gpu_ids: Vec = Vec::new(); - - if let Some(gpu_id) = gpu_id { - gpu_ids.push(gpu_id); - } else { - for (gpu_id, _) in d.get_gpus().unwrap() { - gpu_ids.push(gpu_id); - } - } - - for gpu_id in gpu_ids { - print_fan_curve(&d, gpu_id); - } - } - }, - Opt::Config(config_opt) => match config_opt { - ConfigOpt::Show => print_config(&d), - ConfigOpt::AllowOnlineUpdating => enable_online_update(&d), - ConfigOpt::DisallowOnlineUpdating => disable_online_update(&d), - }, - } -} - -fn disable_online_update(d: &DaemonConnection) { - let mut config = d.get_config().unwrap(); - config.allow_online_update = Some(false); - d.set_config(config).unwrap(); -} - -fn enable_online_update(d: &DaemonConnection) { - let mut config = d.get_config().unwrap(); - config.allow_online_update = Some(true); - d.set_config(config).unwrap(); -} - -fn print_config(d: &DaemonConnection) { - let config = d.get_config().unwrap(); - - println!( - "{} {:?}", - "Online PCI DB updating:".purple(), - config.allow_online_update - ); -} - -fn print_fan_curve(d: &DaemonConnection, gpu_id: u32) { - let fan_control = d.get_fan_control(gpu_id).unwrap(); - - if fan_control.enabled { - println!("{}", "Fan curve:".yellow()); - - for (temp, fan_speed) in fan_control.curve { - println!( - "{}{}: {}{}", - temp.to_string().yellow(), - "C°".yellow(), - fan_speed.round().to_string().bold(), - "%".bold() - ); - } - } else { - println!("{}", "Automatic fan control used".yellow()); - } -} - -fn print_info(d: &DaemonConnection, gpu_id: u32) { - let gpu_info = d.get_gpu_info(gpu_id).unwrap(); - println!( - "{} {}", - "GPU Model:".blue(), - gpu_info.vendor_data.card_model.unwrap_or_default().bold() - ); - println!( - "{} {}", - "GPU Vendor:".blue(), - gpu_info.vendor_data.gpu_vendor.unwrap_or_default().bold() - ); - println!("{} {}", "Driver in use:".blue(), gpu_info.driver.bold()); - println!( - "{} {}", - "VBIOS Version:".blue(), - gpu_info.vbios_version.bold() - ); - println!( - "{} {}", - "VRAM Size:".blue(), - gpu_info.vram_size.to_string().bold() - ); - println!("{} {}", "Link Speed:".blue(), gpu_info.link_speed.bold()); -} - -fn print_stats(d: &DaemonConnection, gpu_id: u32) { - let gpu_stats = d.get_gpu_stats(gpu_id).unwrap(); - println!( - "{} {}/{}{}", - "VRAM Usage:".green(), - gpu_stats.mem_used.unwrap_or_default().to_string().bold(), - gpu_stats.mem_total.unwrap_or_default().to_string().bold(), - "MiB".bold(), - ); - println!( - "{} {}{}", - "Temperature:".green(), - gpu_stats - .temperatures - .get("edge") - .unwrap() - .current - .to_string() - .bold(), - "°C".bold(), - ); - println!( - "{} {}/{}{}", - "Fan Speed:".green(), - gpu_stats.fan_speed.unwrap_or_default().to_string().bold(), - gpu_stats - .max_fan_speed - .unwrap_or_default() - .to_string() - .bold(), - "RPM".bold(), - ); - println!( - "{} {}{}", - "GPU Clock:".green(), - gpu_stats.gpu_freq.unwrap_or_default().to_string().bold(), - "MHz".bold(), - ); - println!( - "{} {}{}", - "GPU Voltage:".green(), - (gpu_stats.voltage.unwrap_or_default() as f64 / 1000.0) - .to_string() - .bold(), - "V".bold(), - ); - println!( - "{} {}{}", - "VRAM Clock:".green(), - gpu_stats.mem_freq.unwrap_or_default().to_string().bold(), - "MHz".bold(), - ); - println!( - "{} {}/{}{}", - "Power Usage:".green(), - gpu_stats.power_avg.unwrap_or_default().to_string().bold(), - gpu_stats.power_cap.unwrap_or_default().to_string().bold(), - "W".bold(), - ); -} diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml deleted file mode 100644 index eb8c3ee9..00000000 --- a/daemon/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "daemon" -version = "0.1.0" -authors = ["Ilya Zlobintsev "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bincode = "1.3" -serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = "1.0" -vulkano = "0.26" -log = "0.4" -env_logger = "0.9" -rand = "0.8" -signal-hook = "0.3" -reqwest = { version = "0.11", features = ["blocking", "json"] } -nix = "0.23" -pciid-parser = { version = "0.6.0", features = ["online"] } diff --git a/daemon/src/config.rs b/daemon/src/config.rs deleted file mode 100644 index 73a30e6e..00000000 --- a/daemon/src/config.rs +++ /dev/null @@ -1,118 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; -use std::fs; -use std::io; -use std::path::PathBuf; - -use crate::gpu_controller::PowerProfile; - -#[derive(Debug)] -pub enum ConfigError { - IoError(io::Error), - ParseError(serde_json::Error), -} - -impl From for ConfigError { - fn from(error: io::Error) -> Self { - ConfigError::IoError(error) - } -} - -impl From for ConfigError { - fn from(error: serde_json::Error) -> Self { - ConfigError::ParseError(error) - } -} - -#[derive(Deserialize, Serialize, Debug, Clone, Hash, Eq)] -pub struct GpuIdentifier { - pub pci_id: String, - pub card_model: Option, - pub gpu_model: Option, - pub path: PathBuf, -} - -impl PartialEq for GpuIdentifier { - fn eq(&self, other: &Self) -> bool { - self.pci_id == other.pci_id - && self.gpu_model == other.gpu_model - && self.card_model == other.card_model - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct GpuConfig { - pub fan_control_enabled: bool, - pub fan_curve: BTreeMap, - pub power_cap: i64, - pub power_profile: PowerProfile, - pub gpu_max_clock: i64, - pub gpu_max_voltage: Option, - pub vram_max_clock: i64, -} - -impl GpuConfig { - pub fn new() -> Self { - let mut fan_curve: BTreeMap = BTreeMap::new(); - fan_curve.insert(20, 0f64); - fan_curve.insert(40, 0f64); - fan_curve.insert(60, 50f64); - fan_curve.insert(80, 80f64); - fan_curve.insert(100, 100f64); - - GpuConfig { - fan_curve, - fan_control_enabled: false, - power_cap: -1, - power_profile: PowerProfile::Auto, - gpu_max_clock: 0, - gpu_max_voltage: None, - vram_max_clock: 0, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub struct Config { - pub gpu_configs: HashMap, - pub allow_online_update: Option, - pub config_path: PathBuf, - pub group: String, -} - -impl Config { - pub fn new(config_path: &PathBuf) -> Self { - let gpu_configs: HashMap = HashMap::new(); - - Config { - gpu_configs, - allow_online_update: None, - config_path: config_path.clone(), - group: String::from("wheel"), - } - } - - pub fn read_from_file(path: &PathBuf) -> Result { - let json = fs::read_to_string(path)?; - - Ok(serde_json::from_str::(&json)?) - } - - pub fn save(&self) -> Result<(), ConfigError> { - let json = serde_json::to_string_pretty(self)?; - log::info!("saving {}", json.to_string()); - - Ok(fs::write(&self.config_path, &json.to_string())?) - } -} - -/*#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn write_config() -> Result<(), ConfigError> { - let c = Config::new(); - c.save(PathBuf::from("/tmp/config.json")) - } -}*/ diff --git a/daemon/src/daemon_connection.rs b/daemon/src/daemon_connection.rs deleted file mode 100644 index 2955c10c..00000000 --- a/daemon/src/daemon_connection.rs +++ /dev/null @@ -1,228 +0,0 @@ -use crate::config::Config; -use crate::gpu_controller::{FanControlInfo, GpuStats}; -use crate::gpu_controller::{GpuInfo, PowerProfile}; -use crate::Daemon; -use crate::DaemonError; -use crate::{Action, DaemonResponse, SOCK_PATH}; -use std::collections::{BTreeMap, HashMap}; - -#[derive(Clone, Copy)] -pub struct DaemonConnection {} - -pub const BUFFER_SIZE: usize = 4096; - -impl DaemonConnection { - pub fn new() -> Result { - let addr = nix::sys::socket::SockAddr::Unix( - nix::sys::socket::UnixAddr::new_abstract(SOCK_PATH.as_bytes()).unwrap(), - ); - let socket = nix::sys::socket::socket( - nix::sys::socket::AddressFamily::Unix, - nix::sys::socket::SockType::Stream, - nix::sys::socket::SockFlag::empty(), - None, - ) - .expect("Creating socket failed"); - nix::sys::socket::connect(socket, &addr).expect("Socket connect failed"); - - nix::unistd::write(socket, &bincode::serialize(&Action::CheckAlive).unwrap()) - .expect("Writing check alive to socket failed"); - - nix::sys::socket::shutdown(socket, nix::sys::socket::Shutdown::Write) - .expect("Could not shut down"); - - let mut buffer = Vec::::new(); - buffer.resize(BUFFER_SIZE, 0); - loop { - match nix::unistd::read(socket, &mut buffer) { - Ok(0) => { - break; - } - Ok(n) => { - assert!(n < buffer.len()); - if n < buffer.len() { - buffer.resize(n, 0); - } - break; - } - Err(e) => { - panic!("Error reading from socket: {}", e); - } - } - } - nix::sys::socket::shutdown(socket, nix::sys::socket::Shutdown::Both) - .expect("Could not shut down"); - - nix::unistd::close(socket).expect("Failed to close"); - - let result: Result = - bincode::deserialize(&buffer).expect("failed to deserialize message"); - - match result { - Ok(_) => Ok(DaemonConnection {}), - Err(_) => Err(DaemonError::ConnectionFailed), - } - } - - fn send_action(&self, action: Action) -> Result { - let addr = nix::sys::socket::SockAddr::Unix( - nix::sys::socket::UnixAddr::new_abstract(SOCK_PATH.as_bytes()).unwrap(), - ); - let socket = nix::sys::socket::socket( - nix::sys::socket::AddressFamily::Unix, - nix::sys::socket::SockType::Stream, - nix::sys::socket::SockFlag::empty(), - None, - ) - .expect("Socket failed"); - nix::sys::socket::connect(socket, &addr).expect("connect failed"); - - let b = bincode::serialize(&action).unwrap(); - nix::unistd::write(socket, &b).expect("Writing action to socket failed"); - - nix::sys::socket::shutdown(socket, nix::sys::socket::Shutdown::Write) - .expect("Could not shut down"); - - let buffer = Daemon::read_buffer(socket); - - nix::sys::socket::shutdown(socket, nix::sys::socket::Shutdown::Both) - .expect("Failed to shut down"); - - nix::unistd::close(socket).expect("Failed to close"); - - bincode::deserialize(&buffer).expect("failed to deserialize message") - } - - pub fn get_gpu_stats(&self, gpu_id: u32) -> Result { - match self.send_action(Action::GetStats(gpu_id))? { - DaemonResponse::GpuStats(stats) => Ok(stats), - _ => unreachable!(), - } - } - - pub fn get_gpu_info(&self, gpu_id: u32) -> Result { - match self.send_action(Action::GetInfo(gpu_id))? { - DaemonResponse::GpuInfo(info) => Ok(info), - _ => unreachable!("impossible enum variant"), - } - } - - pub fn start_fan_control(&self, gpu_id: u32) -> Result<(), DaemonError> { - match self.send_action(Action::StartFanControl(gpu_id))? { - DaemonResponse::OK => Ok(()), - _ => Err(DaemonError::HWMonError), - } - } - - pub fn stop_fan_control(&self, gpu_id: u32) -> Result<(), DaemonError> { - match self.send_action(Action::StopFanControl(gpu_id))? { - DaemonResponse::OK => Ok(()), - _ => Err(DaemonError::HWMonError), - } - } - - pub fn get_fan_control(&self, gpu_id: u32) -> Result { - match self.send_action(Action::GetFanControl(gpu_id))? { - DaemonResponse::FanControlInfo(info) => Ok(info), - _ => unreachable!(), - } - } - - pub fn set_fan_curve(&self, gpu_id: u32, curve: BTreeMap) -> Result<(), DaemonError> { - match self.send_action(Action::SetFanCurve(gpu_id, curve))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - pub fn set_power_cap(&self, gpu_id: u32, cap: i64) -> Result<(), DaemonError> { - match self.send_action(Action::SetPowerCap(gpu_id, cap))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - pub fn set_power_profile(&self, gpu_id: u32, profile: PowerProfile) -> Result<(), DaemonError> { - match self.send_action(Action::SetPowerProfile(gpu_id, profile))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - /*pub fn set_gpu_power_state(&self, gpu_id: u32, num: u32, clockspeed: i64, voltage: Option) -> Result<(), DaemonError> { - match self.send_action(Action::SetGPUPowerState(gpu_id, num, clockspeed, voltage))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - }*/ - - pub fn set_gpu_max_power_state( - &self, - gpu_id: u32, - clockspeed: i64, - voltage: Option, - ) -> Result<(), DaemonError> { - match self.send_action(Action::SetGPUMaxPowerState(gpu_id, clockspeed, voltage))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - pub fn set_vram_max_clock(&self, gpu_id: u32, clockspeed: i64) -> Result<(), DaemonError> { - match self.send_action(Action::SetVRAMMaxClock(gpu_id, clockspeed))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - pub fn commit_gpu_power_states(&self, gpu_id: u32) -> Result<(), DaemonError> { - match self.send_action(Action::CommitGPUPowerStates(gpu_id))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - pub fn reset_gpu_power_states(&self, gpu_id: u32) -> Result<(), DaemonError> { - match self.send_action(Action::ResetGPUPowerStates(gpu_id))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } - - pub fn get_gpus(&self) -> Result>, DaemonError> { - match self.send_action(Action::GetGpus)? { - DaemonResponse::Gpus(gpus) => Ok(gpus), - _ => unreachable!(), - } - } - - pub fn shutdown(&self) { - let addr = nix::sys::socket::SockAddr::Unix( - nix::sys::socket::UnixAddr::new_abstract(SOCK_PATH.as_bytes()).unwrap(), - ); - let socket = nix::sys::socket::socket( - nix::sys::socket::AddressFamily::Unix, - nix::sys::socket::SockType::Stream, - nix::sys::socket::SockFlag::empty(), - None, - ) - .expect("Socket failed"); - nix::sys::socket::connect(socket, &addr).expect("connect failed"); - nix::unistd::write(socket, &mut &bincode::serialize(&Action::Shutdown).unwrap()) - .expect("Writing shutdown to socket failed"); - } - - pub fn get_config(&self) -> Result { - match self.send_action(Action::GetConfig)? { - DaemonResponse::Config(config) => Ok(config), - _ => unreachable!(), - } - } - - pub fn set_config(&self, config: Config) -> Result<(), DaemonError> { - match self.send_action(Action::SetConfig(config))? { - DaemonResponse::OK => Ok(()), - _ => unreachable!(), - } - } -} diff --git a/daemon/src/gpu_controller.rs b/daemon/src/gpu_controller.rs deleted file mode 100644 index 518bdf16..00000000 --- a/daemon/src/gpu_controller.rs +++ /dev/null @@ -1,1120 +0,0 @@ -use crate::{ - config::{GpuConfig, GpuIdentifier}, - hw_mon::{HWMon, HWMonError, Temperature}, -}; -use pciid_parser::{schema::DeviceInfo, Database}; -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; -use std::fs; -use std::num::ParseIntError; -use std::path::PathBuf; -use vulkano::device::physical::PhysicalDevice; -use vulkano::instance::{Instance, InstanceExtensions}; - -#[derive(Serialize, Deserialize, Debug)] -pub enum GpuControllerError { - NotSupported, - PermissionDenied, - UnknownError, - ParseError(String), -} - -impl From for GpuControllerError { - fn from(err: std::io::Error) -> GpuControllerError { - match err.kind() { - std::io::ErrorKind::PermissionDenied => GpuControllerError::PermissionDenied, - std::io::ErrorKind::NotFound => GpuControllerError::NotSupported, - _ => GpuControllerError::UnknownError, - } - } -} - -impl From for GpuControllerError { - fn from(err: ParseIntError) -> GpuControllerError { - GpuControllerError::ParseError(err.to_string()) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub enum PowerProfile { - Auto, - Low, - High, -} - -impl Default for PowerProfile { - fn default() -> Self { - PowerProfile::Auto - } -} - -impl PowerProfile { - pub fn from_str(profile: &str) -> Result { - match profile { - "auto" | "Automatic" => Ok(PowerProfile::Auto), - "high" | "Highest Clocks" => Ok(PowerProfile::High), - "low" | "Lowest Clocks" => Ok(PowerProfile::Low), - _ => Err(GpuControllerError::ParseError( - "unrecognized GPU power profile".to_string(), - )), - } - } - - pub fn to_string(&self) -> String { - match self { - PowerProfile::Auto => "auto".to_string(), - PowerProfile::High => "high".to_string(), - PowerProfile::Low => "low".to_string(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum ClocksTable { - Old(ClocksTableOld), - New(ClocksTableNew), -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct ClocksTableOld { - pub gpu_power_levels: BTreeMap, // - pub mem_power_levels: BTreeMap, - pub gpu_clocks_range: (i64, i64), - pub mem_clocks_range: (i64, i64), - pub voltage_range: (i64, i64), //IN MILLIVOLTS -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct ClocksTableNew { - pub current_gpu_clocks: (i64, i64), - pub current_max_mem_clock: i64, - // pub vddc_curve: [(i64, i64); 3], - pub gpu_clocks_range: (i64, i64), - pub mem_clocks_range: (i64, i64), - // pub voltage_range: (i64, i64), //IN MILLIVOLTS -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GpuStats { - pub mem_used: Option, - pub mem_total: Option, - pub mem_freq: Option, - pub gpu_freq: Option, - pub temperatures: HashMap, - pub power_avg: Option, - pub power_cap: Option, - pub power_cap_max: Option, - pub fan_speed: Option, - pub max_fan_speed: Option, - pub voltage: Option, - pub gpu_usage: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct FanControlInfo { - pub enabled: bool, - pub curve: BTreeMap, -} -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct VulkanInfo { - pub device_name: String, - pub api_version: String, - pub features: HashMap, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct GpuInfo { - pub vendor_data: VendorData, - pub model_id: String, - pub vendor_id: String, - pub driver: String, - pub vbios_version: String, - pub vram_size: u64, //in MiB - pub link_speed: String, - pub link_width: u8, - pub vulkan_info: VulkanInfo, - pub pci_slot: String, - pub power_profile: Option, - pub clocks_table: Option, - pub power_cap: Option, - pub power_cap_max: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone, Default)] -pub struct VendorData { - pub gpu_vendor: Option, - pub gpu_model: Option, - pub card_vendor: Option, - pub card_model: Option, -} - -impl From> for VendorData { - fn from(info: DeviceInfo) -> Self { - Self { - gpu_vendor: info.vendor_name.map(str::to_owned), - gpu_model: info.device_name.map(str::to_owned), - card_vendor: info.subvendor_name.map(str::to_owned), - card_model: info.subdevice_name.map(str::to_owned), - } - } -} - -#[derive(Deserialize, Serialize)] -pub struct GpuController { - pub hw_path: PathBuf, - hw_mon: Option, - gpu_info: GpuInfo, - config: GpuConfig, -} - -impl GpuController { - pub fn new(hw_path: PathBuf, config: GpuConfig, pci_db: &Option) -> Self { - let mut controller = GpuController { - hw_path: hw_path.clone(), - hw_mon: None, - config: GpuConfig::new(), - gpu_info: GpuInfo::default(), - }; - - controller.gpu_info = controller.get_info_initial(pci_db); - - controller.load_config(&config); - - controller - } - - pub fn load_config(&mut self, config: &GpuConfig) { - self.hw_mon = match fs::read_dir(self.hw_path.join("hwmon")) { - Ok(mut path) => { - let path = path.next().unwrap().unwrap().path(); - let hw_mon = HWMon::new( - &path, - config.fan_control_enabled, - config.fan_curve.clone(), - Some(config.power_cap), - ); - Some(hw_mon) - } - _ => None, - }; - - #[allow(unused_must_use)] - { - self.set_power_profile(config.power_profile.clone()); - - self.set_gpu_max_power_state(config.gpu_max_clock, config.gpu_max_voltage); - - self.set_vram_max_clockspeed(config.vram_max_clock); - - self.commit_gpu_power_states(); - } - } - - pub fn get_config(&self) -> GpuConfig { - self.config.clone() - } - - pub fn get_identifier(&self) -> GpuIdentifier { - let gpu_info = self.get_info(); - GpuIdentifier { - pci_id: gpu_info.pci_slot.clone(), - card_model: gpu_info.vendor_data.card_model.clone(), - gpu_model: gpu_info.vendor_data.gpu_model.clone(), - path: self.hw_path.clone(), - } - } - - pub fn get_info(&self) -> GpuInfo { - let mut info = self.gpu_info.clone(); - - info.power_profile = match self.get_power_profile() { - Ok(p) => Some(p), - Err(_) => None, - }; - - info.clocks_table = match self.get_clocks_table() { - Ok(t) => Some(t), - Err(_) => None, - }; - - if let Some(hw_mon) = &self.hw_mon { - info.power_cap = hw_mon.get_power_cap(); - info.power_cap_max = hw_mon.get_power_cap_max(); - } - - info - } - - fn get_info_initial(&self, pci_db: &Option) -> GpuInfo { - let uevent = - fs::read_to_string(self.hw_path.join("uevent")).expect("Failed to read uevent"); - - let mut driver = String::new(); - let mut vendor_id = String::new(); - let mut model_id = String::new(); - let mut card_vendor_id = String::new(); - let mut card_model_id = String::new(); - let mut pci_slot = String::new(); - - for line in uevent.split('\n') { - let split = line.split('=').collect::>(); - match split.get(0).unwrap() { - &"DRIVER" => driver = split.get(1).unwrap().to_string(), - &"PCI_ID" => { - let ids = split - .last() - .expect("failed to get split") - .split(':') - .collect::>(); - vendor_id = ids.get(0).unwrap().to_string(); - model_id = ids.get(1).unwrap().to_string(); - } - &"PCI_SUBSYS_ID" => { - let ids = split - .last() - .expect("failed to get split") - .split(':') - .collect::>(); - card_vendor_id = ids.get(0).unwrap().to_string(); - card_model_id = ids.get(1).unwrap().to_string(); - } - &"PCI_SLOT_NAME" => pci_slot = split.get(1).unwrap().to_string(), - _ => (), - } - } - - let vbios_version = match fs::read_to_string(self.hw_path.join("vbios_version")) { - Ok(v) => v, - Err(_) => "".to_string(), - } - .trim() - .to_string(); - - let vram_size = match fs::read_to_string(self.hw_path.join("mem_info_vram_total")) { - Ok(a) => a.trim().parse::().unwrap() / 1024 / 1024, - Err(_) => 0, - }; - - let link_speed = match fs::read_to_string(self.hw_path.join("current_link_speed")) { - Ok(a) => a.trim().to_string(), - Err(_) => "".to_string(), - }; - - let link_width = match fs::read_to_string(self.hw_path.join("current_link_width")) { - Ok(a) => a.trim().parse::().unwrap(), - Err(_) => 0, - }; - - let vulkan_info = GpuController::get_vulkan_info(&model_id); - - let vendor_data = match pci_db { - Some(db) => db - .get_device_info(&vendor_id, &model_id, &card_vendor_id, &card_model_id) - .into(), - None => match Database::read() { - Ok(db) => db - .get_device_info(&vendor_id, &model_id, &card_vendor_id, &card_model_id) - .into(), - Err(err) => { - println!( - "{:?} pci.ids not found! Make sure you have 'hwdata' installed", - err - ); - VendorData::default() - } - }, - }; - - log::info!("Vendor data: {:?}", vendor_data); - - GpuInfo { - vendor_data, - model_id, - vendor_id, - driver, - vbios_version, - vram_size, - link_speed, - link_width, - vulkan_info, - pci_slot, - power_profile: None, - clocks_table: None, - power_cap: None, - power_cap_max: None, - } - } - - pub fn get_stats(&self) -> Result { - let mem_total = match fs::read_to_string(self.hw_path.join("mem_info_vram_total")) { - Ok(a) => Some(a.trim().parse::().unwrap() / 1024 / 1024), - Err(_) => None, - }; - - let mem_used = match fs::read_to_string(self.hw_path.join("mem_info_vram_used")) { - Ok(a) => Some(a.trim().parse::().unwrap() / 1024 / 1024), - Err(_) => None, - }; - - let gpu_usage = match fs::read_to_string(self.hw_path.join("gpu_busy_percent")) { - Ok(a) => Some(a.trim().parse::().unwrap()), - Err(_) => None, - }; - - let ( - mem_freq, - gpu_freq, - temperatures, - power_avg, - power_cap, - power_cap_max, - fan_speed, - max_fan_speed, - voltage, - ) = match &self.hw_mon { - Some(hw_mon) => ( - hw_mon.get_mem_freq(), - hw_mon.get_gpu_freq(), - hw_mon.get_temps(), - hw_mon.get_power_avg(), - hw_mon.get_power_cap(), - hw_mon.get_power_cap_max(), - hw_mon.get_fan_speed(), - hw_mon.get_fan_max_speed(), - hw_mon.get_voltage(), - ), - None => return Err(HWMonError::NoHWMon), - }; - - Ok(GpuStats { - mem_total, - mem_used, - mem_freq, - gpu_freq, - temperatures, - power_avg, - power_cap, - power_cap_max, - fan_speed, - max_fan_speed, - voltage, - gpu_usage, - }) - } - - pub fn start_fan_control(&mut self) -> Result<(), HWMonError> { - match &self.hw_mon { - Some(hw_mon) => match hw_mon.start_fan_control() { - Ok(_) => { - self.config.fan_control_enabled = true; - Ok(()) - } - Err(e) => Err(e), - }, - None => Err(HWMonError::NoHWMon), - } - } - - pub fn stop_fan_control(&mut self) -> Result<(), HWMonError> { - match &self.hw_mon { - Some(hw_mon) => match hw_mon.stop_fan_control() { - Ok(_) => { - self.config.fan_control_enabled = false; - Ok(()) - } - Err(e) => Err(e), - }, - None => Err(HWMonError::NoHWMon), - } - } - - pub fn get_fan_control(&self) -> Result { - match &self.hw_mon { - Some(hw_mon) => match hw_mon.get_fan_speed() { - Some(_) => { - let control = hw_mon.get_fan_control(); - Ok(FanControlInfo { - enabled: control.0, - curve: control.1, - }) - } - None => Err(HWMonError::Unsupported), - }, - None => Err(HWMonError::NoHWMon), - } - } - - pub fn set_fan_curve(&mut self, curve: BTreeMap) -> Result<(), HWMonError> { - match &self.hw_mon { - Some(hw_mon) => { - hw_mon.set_fan_curve(curve.clone()); - self.config.fan_curve = curve; - Ok(()) - } - None => Err(HWMonError::NoHWMon), - } - } - - pub fn set_power_cap(&mut self, cap: i64) -> Result<(), HWMonError> { - match &mut self.hw_mon { - Some(hw_mon) => { - hw_mon.set_power_cap(cap).unwrap(); - self.config.power_cap = cap; - Ok(()) - } - None => Err(HWMonError::NoHWMon), - } - } - - pub fn get_power_cap(&self) -> Result<(i64, i64), HWMonError> { - match &self.hw_mon { - Some(hw_mon) => { - let min = hw_mon - .get_power_cap() - .ok_or_else(|| HWMonError::Unsupported)?; - let max = hw_mon - .get_power_cap_max() - .ok_or_else(|| HWMonError::Unsupported)?; - - Ok((min, max)) - } - None => Err(HWMonError::NoHWMon), - } - } - - fn get_power_profile(&self) -> Result { - match fs::read_to_string(self.hw_path.join("power_dpm_force_performance_level")) { - Ok(s) => Ok(PowerProfile::from_str(&s.trim()).unwrap()), - Err(_) => Err(GpuControllerError::NotSupported), - } - } - - pub fn set_power_profile(&mut self, profile: PowerProfile) -> Result<(), GpuControllerError> { - match fs::write( - self.hw_path.join("power_dpm_force_performance_level"), - profile.to_string(), - ) { - Ok(_) => { - self.config.power_profile = profile; - Ok(()) - } - Err(_) => Err(GpuControllerError::NotSupported), - } - } - - fn get_clocks_table(&self) -> Result { - match fs::read_to_string(self.hw_path.join("pp_od_clk_voltage")) { - Ok(table) => Self::parse_clocks_table(&table), - Err(_) => Err(GpuControllerError::NotSupported), - } - } - - fn parse_clocks_table(table: &str) -> Result { - if table.contains("CURVE") { - Ok(ClocksTable::New(Self::parse_clocks_table_new(table)?)) - } else { - Ok(ClocksTable::Old(Self::parse_clocks_table_old(table)?)) - } - } - - fn parse_clocks_table_old(table: &str) -> Result { - let mut clocks_table = ClocksTableOld::default(); - - let mut lines_iter = table.trim().split("\n").into_iter(); - - log::trace!("Reading clocks table"); - - while let Some(line) = lines_iter.next() { - let line = line.trim(); - log::trace!("Parsing line {}", line); - - match line { - "OD_SCLK:" | "OD_MCLK:" => { - let is_vram = match line { - "OD_SCLK:" => false, - "OD_MCLK:" => true, - _ => unreachable!(), - }; - - log::trace!("Parsing clock levels"); - - // If `next()` is used on the main iterator directly, it will consume the `OD_MCLK:` aswell, - // which means the outer loop won't recognize that the next lines are of a different clock type. - // Thus, it is better to count how many lines were of the clock levels and then substract that amount from the main iterator. - let mut i = 0; - let mut lines = lines_iter.clone(); - - while let Some(line) = lines.next() { - let line = line.trim(); - log::trace!("Parsing power level line {}", line); - - // Probably shouldn't unwrap, will fail on empty lines in clocks table - if let Some(_) = line.chars().next().unwrap().to_digit(10) { - let (num, clock, voltage) = - GpuController::parse_clock_voltage_line(line)?; - - log::trace!("Power level {}: {}MHz {}mV", num, clock, voltage); - - if is_vram { - clocks_table.mem_power_levels.insert(num, (clock, voltage)); - } else { - clocks_table.gpu_power_levels.insert(num, (clock, voltage)); - } - - i += 1; - } else { - // Probably a better way to do this - for _ in 0..i { - lines_iter.next().unwrap(); - } - log::trace!("Finished reading clock levels"); - break; - } - } - } - "OD_RANGE:" => { - log::trace!("Parsing clock and voltage ranges"); - - while let Some(line) = lines_iter.next() { - let mut split = line.split_whitespace(); - - let name = split.next().ok_or_else(|| { - GpuControllerError::ParseError("failed to get range name".to_string()) - })?; - let min = split.next().ok_or_else(|| { - GpuControllerError::ParseError( - "failed to get range minimal value".to_string(), - ) - })?; - let max = split.next().ok_or_else(|| { - GpuControllerError::ParseError( - "failed to get range maximum value".to_string(), - ) - })?; - - match name { - "SCLK:" => { - let min_clock: i64 = min.replace("MHz", "").parse()?; - let max_clock: i64 = max.replace("MHz", "").parse()?; - - clocks_table.gpu_clocks_range = (min_clock, max_clock); - } - "MCLK:" => { - let min_clock: i64 = min.replace("MHz", "").parse()?; - let max_clock: i64 = max.replace("MHz", "").parse()?; - - clocks_table.mem_clocks_range = (min_clock, max_clock); - } - "VDDC:" => { - let min_voltage: i64 = min.replace("mV", "").parse()?; - let max_voltage: i64 = max.replace("mV", "").parse()?; - - clocks_table.voltage_range = (min_voltage, max_voltage); - } - _ => { - return Err(GpuControllerError::ParseError( - "unrecognized voltage range type".to_string(), - )) - } - } - } - } - _ => { - return Err(GpuControllerError::ParseError( - "unrecognized line type".to_string(), - )) - } - } - } - - log::trace!("Successfully parsed the clocks table"); - Ok(clocks_table) - } - - fn parse_clocks_table_new(table: &str) -> Result { - log::trace!("Detected clocks table format for Vega20 or newer"); - - let mut clocks_table = ClocksTableNew::default(); - - let mut lines_iter = table.trim().split("\n").into_iter(); - - log::trace!("Reading clocks table"); - - while let Some(line) = &lines_iter.next() { - let line = line.trim(); - log::trace!("Parsing line {}", line); - - match line { - "OD_SCLK:" => { - let min_clock_line = lines_iter - .next() - .ok_or_else(|| { - GpuControllerError::ParseError( - "unexpeceted clocks file end".to_string(), - ) - })? - .trim() - .to_lowercase(); - - let min_clock: i64 = min_clock_line - .strip_prefix("0:") - .ok_or_else(|| { - GpuControllerError::ParseError(format!( - "invalid clock line prefix in {}", - min_clock_line - )) - })? - .strip_suffix("mhz") - .ok_or_else(|| { - GpuControllerError::ParseError(format!( - "invalid clock line suffix in {}", - min_clock_line - )) - })? - .trim() - .parse()?; - - let max_clock_line = lines_iter - .next() - .ok_or_else(|| { - GpuControllerError::ParseError( - "unexpeceted clocks file end".to_string(), - ) - })? - .trim() - .to_lowercase(); - - let max_clock: i64 = max_clock_line - .strip_prefix("1:") - .ok_or_else(|| { - GpuControllerError::ParseError(format!( - "invalid clock line prefix in {}", - min_clock_line - )) - })? - .strip_suffix("mhz") - .ok_or_else(|| { - GpuControllerError::ParseError(format!( - "invalid clock line suffix in {}", - min_clock_line - )) - })? - .trim() - .parse()?; - - clocks_table.current_gpu_clocks = (min_clock, max_clock); - } - "OD_MCLK:" => { - let max_clock_line = lines_iter - .next() - .ok_or_else(|| { - GpuControllerError::ParseError("unexpected clocks file end".to_string()) - })? - .trim() - .to_lowercase(); - - let max_clock = max_clock_line - .strip_prefix("1:") - .ok_or_else(|| { - GpuControllerError::ParseError(format!( - "invalid clock line prefix in {}", - max_clock_line - )) - })? - .strip_suffix("mhz") - .ok_or_else(|| { - GpuControllerError::ParseError(format!( - "invalid clock line suffix in {}", - max_clock_line - )) - })? - .trim() - .parse()?; - - clocks_table.current_max_mem_clock = max_clock; - } - "OD_RANGE:" => { - while let Some(line) = &lines_iter.next() { - let line = line.trim(); - log::trace!("Parsing OD_RANGE line {}", &line); - - match &line[..5] { - "SCLK:" => { - let mut split = line.split_whitespace(); - - // Skips the 'SCLK' - split.next().unwrap(); - - let min_clock = split - .next() - .unwrap() - .strip_suffix("Mhz") - .ok_or_else(|| { - GpuControllerError::ParseError("missing suffix".to_string()) - })? - .parse()?; - - let max_clock = split - .next() - .unwrap() - .strip_suffix("Mhz") - .ok_or_else(|| { - GpuControllerError::ParseError("missing suffix".to_string()) - })? - .parse()?; - - clocks_table.gpu_clocks_range = (min_clock, max_clock); - } - "MCLK:" => { - let mut split = line.split_whitespace(); - - // Skips the 'SCLK' - split.next().unwrap(); - - let min_clock = split - .next() - .unwrap() - .strip_suffix("Mhz") - .ok_or_else(|| { - GpuControllerError::ParseError("missing suffix".to_string()) - })? - .parse()?; - - let max_clock = split - .next() - .unwrap() - .strip_suffix("Mhz") - .ok_or_else(|| { - GpuControllerError::ParseError("missing suffix".to_string()) - })? - .parse()?; - - clocks_table.mem_clocks_range = (min_clock, max_clock); - } - _ => { - log::trace!("OD_RANGE ended"); - break; - } - } - } - } - _ => { - log::trace!("Skipping line"); - continue; - } - } - } - - Ok(clocks_table) - } - - /*pub fn set_gpu_power_state( - &mut self, - num: u32, - clockspeed: i64, - voltage: Option, - ) -> Result<(), GpuControllerError> { - let mut line = format!("s {} {}", num, clockspeed); - - if let Some(voltage) = voltage { - line.push_str(&format!(" {}", voltage)); - } - line.push_str("\n"); - - log::info!("Setting gpu power state {}", line); - log::info!("Writing {} to pp_od_clk_voltage", line); - - fs::write(self.hw_path.join("pp_od_clk_voltage"), line)?; - - self.config - .gpu_power_states - .insert(num, (clockspeed, voltage.unwrap())); - - Ok(()) - }*/ - - pub fn set_gpu_max_power_state( - &mut self, - clockspeed: i64, - voltage: Option, - ) -> Result<(), GpuControllerError> { - match self.get_clocks_table()? { - ClocksTable::Old(clocks_table) => { - let profile = { clocks_table.gpu_power_levels.iter().next_back().unwrap().0 }; - - let mut line = format!("s {} {}", profile, clockspeed); - - if let Some(voltage) = voltage { - line.push_str(&format!(" {}", voltage)); - } - line.push_str("\n"); - - log::info!("Writing {} to pp_od_clk_voltage", line); - - fs::write(self.hw_path.join("pp_od_clk_voltage"), line)?; - - self.config.gpu_max_clock = clockspeed; - self.config.gpu_max_voltage = voltage; - } - ClocksTable::New(_) => { - let s_line = format!("s 1 {}\n", clockspeed); - - fs::write(self.hw_path.join("pp_od_clk_voltage"), s_line)?; - - if let Some(voltage) = voltage { - let vc_line = format!("vc 2 {} {}\n", clockspeed, voltage); - - fs::write(self.hw_path.join("pp_od_clk_voltage"), vc_line)?; - } - } - } - - Ok(()) - } - - pub fn set_vram_max_clockspeed(&mut self, clockspeed: i64) -> Result<(), GpuControllerError> { - match self.get_clocks_table()? { - ClocksTable::Old(clocks_table) => { - let (profile, voltage) = { - let power_level = clocks_table.mem_power_levels.iter().next_back().unwrap(); - log::info!("Using mem power level {:?}", power_level); - (power_level.0, power_level.1 .1) - }; - - let line = format!("m {} {} {}\n", profile, clockspeed, voltage); - - log::info!("Writing {} to pp_od_clk_voltage", line); - - fs::write(self.hw_path.join("pp_od_clk_voltage"), line)?; - - self.config.vram_max_clock = clockspeed; - } - ClocksTable::New(_) => { - let s_line = format!("m 1 {}\n", clockspeed); - - fs::write(self.hw_path.join("pp_od_clk_voltage"), s_line)?; - } - } - - Ok(()) - } - - pub fn commit_gpu_power_states(&mut self) -> Result<(), GpuControllerError> { - fs::write(self.hw_path.join("pp_od_clk_voltage"), b"c\n")?; - Ok(()) - } - - pub fn reset_gpu_power_states(&mut self) -> Result<(), GpuControllerError> { - fs::write(self.hw_path.join("pp_od_clk_voltage"), b"r\n")?; - Ok(()) - } - - fn get_vulkan_info(pci_id: &str) -> VulkanInfo { - let mut device_name = String::from("Not supported"); - let mut api_version = String::new(); - let mut features = HashMap::new(); - - let pci_id = u32::from_str_radix(pci_id, 16).expect("Invalid device ID"); - - match Instance::new( - None, - vulkano::Version::V1_5, - &InstanceExtensions::none(), - None, - ) { - Ok(instance) => { - for physical in PhysicalDevice::enumerate(&instance) { - let properties = physical.properties(); - - if properties.device_id == pci_id { - api_version = physical.api_version().to_string(); - device_name = properties.device_name.clone(); - - let features_string = format!("{:?}", physical.supported_features()); - let features_string = features_string - .replace("Features", "") - .replace("{", "") - .replace("}", ""); - - for feature in features_string.split(',') { - // let (name, supported) = feature.split_once(':').unwrap(); Use this once it's in stable - let mut split = feature.split(':'); - let name = split.next().unwrap().trim(); - let supported = split.next().unwrap().trim(); - - let supported: bool = supported.parse().unwrap(); - - features.insert(name.to_string(), supported); - } - - break; - } - } - } - Err(_) => (), - } - - VulkanInfo { - device_name, - api_version, - features, - } - } - - fn parse_clock_voltage_line(line: &str) -> Result<(u32, i64, i64), GpuControllerError> { - log::trace!("Parsing line {}", line); - - let line = line.to_uppercase(); - let line_parts: Vec<&str> = line.split_whitespace().collect(); - - let num: u32 = line_parts - .get(0) - .ok_or_else(|| { - GpuControllerError::ParseError("failed to read the power level number".to_string()) - })? - .chars() - .nth(0) - .unwrap() - .to_digit(10) - .unwrap(); - let clock: i64 = line_parts - .get(1) - .ok_or_else(|| { - GpuControllerError::ParseError("failed to read the clockspeed".to_string()) - })? - .strip_suffix("MHZ") - .ok_or_else(|| GpuControllerError::ParseError("failed to strip \"MHZ\"".to_string()))? - .parse()?; - let voltage: i64 = line_parts - .get(2) - .ok_or_else(|| { - GpuControllerError::ParseError("failed to read the voltage".to_string()) - })? - .strip_suffix("MV") - .ok_or_else(|| GpuControllerError::ParseError("failed to strip \"mV\"".to_string()))? - .parse()?; - - Ok((num, clock, voltage)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn init() { - let _ = env_logger::builder().is_test(true).try_init(); - } - - // pp_od_clk_voltage taken from an RX 580 - #[test] - fn parse_clocks_table_polaris() { - init(); - - let pp_od_clk_voltage = r#" - OD_SCLK: - 0: 300MHz 750mV - 1: 600MHz 769mV - 2: 900MHz 912mV - 3: 1145MHz 1125mV - 4: 1215MHz 1150mV - 5: 1257MHz 1150mV - 6: 1300MHz 1150mV - 7: 1366MHz 1150mV - OD_MCLK: - 0: 300MHz 750mV - 1: 1000MHz 825mV - 2: 1750MHz 975mV - OD_RANGE: - SCLK: 300MHz 2000MHz - MCLK: 300MHz 2250MHz - VDDC: 750mV 1200mV"#; - - match GpuController::parse_clocks_table(pp_od_clk_voltage).unwrap() { - ClocksTable::Old(clocks_table) => { - log::trace!("{:?}", clocks_table); - - assert_eq!(clocks_table.gpu_clocks_range, (300, 2000)); - assert_eq!(clocks_table.mem_clocks_range, (300, 2250)); - assert_eq!(clocks_table.voltage_range, (750, 1200)); - - assert_eq!( - clocks_table.gpu_power_levels.get(&6).unwrap(), - &(1300, 1150) - ); - assert_eq!(clocks_table.mem_power_levels.get(&1).unwrap(), &(1000, 825)); - } - _ => panic!("Invalid clocks format detected"), - } - } - - // pp_od_clk_voltage taken from a Vega 56 - #[test] - fn parse_clocks_table_vega() { - init(); - - let pp_od_clk_voltage = r#" - OD_SCLK: - 0: 852Mhz 800mV - 1: 991Mhz 900mV - 2: 1138Mhz 950mV - 3: 1269Mhz 1000mV - 4: 1312Mhz 1050mV - 5: 1474Mhz 1100mV - 6: 1538Mhz 1150mV - 7: 1590Mhz 1157mV - OD_MCLK: - 0: 167Mhz 800mV - 1: 500Mhz 800mV - 2: 700Mhz 900mV - 3: 900Mhz 950mV - OD_RANGE: - SCLK: 852MHz 2400MHz - MCLK: 167MHz 1500MHz - VDDC: 800mV 1200mV"#; - - let clocks_table = GpuController::parse_clocks_table(pp_od_clk_voltage).unwrap(); - - log::trace!("{:?}", clocks_table); - } - - // pp_od_clk_voltage taken from an RX 5700 XT - #[test] - fn parse_clocks_table_navi() { - init(); - - let pp_od_clk_voltage = r#" - OD_SCLK: - 0: 800Mhz - 1: 2100Mhz - OD_MCLK: - 1: 875MHz - OD_VDDC_CURVE: - 0: 800MHz 711mV - 1: 1450MHz 801mV - 2: 2100MHz 1191mV - OD_RANGE: - SCLK: 800Mhz 2150Mhz - MCLK: 625Mhz 950Mhz - VDDC_CURVE_SCLK[0]: 800Mhz 2150Mhz - VDDC_CURVE_VOLT[0]: 750mV 1200mV - VDDC_CURVE_SCLK[1]: 800Mhz 2150Mhz - VDDC_CURVE_VOLT[1]: 750mV 1200mV - VDDC_CURVE_SCLK[2]: 800Mhz 2150Mhz - VDDC_CURVE_VOLT[2]: 750mV 1200mV - "#; - - match GpuController::parse_clocks_table(pp_od_clk_voltage).unwrap() { - ClocksTable::New(clocks_table) => { - log::trace!("{:?}", clocks_table); - - assert_eq!(clocks_table.gpu_clocks_range, (800, 2150)); - assert_eq!(clocks_table.mem_clocks_range, (625, 950)); - - assert_eq!(clocks_table.current_gpu_clocks, (800, 2100)); - assert_eq!(clocks_table.current_max_mem_clock, 875); - } - _ => panic!("Invalid clocks format detected"), - } - } -} diff --git a/daemon/src/hw_mon.rs b/daemon/src/hw_mon.rs deleted file mode 100644 index 2729a615..00000000 --- a/daemon/src/hw_mon.rs +++ /dev/null @@ -1,306 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; -use std::path::PathBuf; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, RwLock}; -use std::time::Duration; -use std::{fs, thread}; - -#[derive(Serialize, Deserialize, Debug, Clone, Copy)] -pub struct Temperature { - pub current: i64, - pub crit: i64, - pub crit_hyst: i64, -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum HWMonError { - PermissionDenied, - InvalidValue, - Unsupported, - NoHWMon, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct HWMon { - hwmon_path: PathBuf, - fan_control: Arc, - fan_curve: Arc>>, -} - -impl HWMon { - pub fn new( - hwmon_path: &PathBuf, - fan_control_enabled: bool, - fan_curve: BTreeMap, - power_cap: Option, - ) -> HWMon { - let mut mon = HWMon { - hwmon_path: hwmon_path.clone(), - fan_control: Arc::new(AtomicBool::new(false)), - fan_curve: Arc::new(RwLock::new(fan_curve)), - }; - - if fan_control_enabled { - mon.start_fan_control().unwrap(); - } - if let Some(cap) = power_cap { - #[allow(unused_must_use)] - { - mon.set_power_cap(cap); - } - } - - mon - } - - pub fn get_fan_max_speed(&self) -> Option { - match fs::read_to_string(self.hwmon_path.join("fan1_max")) { - Ok(speed) => Some(speed.trim().parse().unwrap()), - Err(_) => None, - } - } - - pub fn get_fan_speed(&self) -> Option { - /*if self.fan_control.load(Ordering::SeqCst) { - let pwm1 = fs::read_to_string(self.hwmon_path.join("pwm1")) - .expect("Couldn't read pwm1") - .trim() - .parse::() - .unwrap(); - - self.fan_max_speed / 255 * pwm1 - } - else { - fs::read_to_string(self.hwmon_path.join("fan1_input")) - .expect("Couldn't read fan speed") - .trim() - .parse::() - .unwrap() - }*/ - match fs::read_to_string(self.hwmon_path.join("fan1_input")) { - Ok(a) => Some(a.trim().parse::().unwrap()), - _ => None, - } - } - - pub fn get_mem_freq(&self) -> Option { - let filename = self.hwmon_path.join("freq2_input"); - - match fs::read_to_string(filename) { - Ok(freq) => Some(freq.trim().parse::().unwrap() / 1000 / 1000), - Err(_) => None, - } - } - - pub fn get_gpu_freq(&self) -> Option { - let filename = self.hwmon_path.join("freq1_input"); - - match fs::read_to_string(filename) { - Ok(freq) => Some(freq.trim().parse::().unwrap() / 1000 / 1000), - Err(_) => None, - } - } - - pub fn get_temps(&self) -> HashMap { - let mut temps = HashMap::new(); - - for i in 1..3 { - let label_filename = self.hwmon_path.join(format!("temp{}_label", i)); - - match fs::read_to_string(label_filename) { - Ok(label) => { - // If there's a label identifying the sensor, there should always be input and crit files too. But just in case using .unwrap_or_default() - let current = { - let filename = self.hwmon_path.join(format!("temp{}_input", i)); - fs::read_to_string(filename) - .unwrap_or_default() - .trim() - .parse::() - .unwrap_or_default() - / 1000 - }; - - let crit = { - let filename = self.hwmon_path.join(format!("temp{}_crit", i)); - fs::read_to_string(filename) - .unwrap_or_default() - .trim() - .parse::() - .unwrap_or_default() - / 1000 - }; - - let crit_hyst = { - let filename = self.hwmon_path.join(format!("temp{}_crit_hyst", i)); - fs::read_to_string(filename) - .unwrap_or_default() - .trim() - .parse::() - .unwrap_or_default() - / 1000 - }; - - temps.insert( - label.trim().to_string(), - Temperature { - current, - crit, - crit_hyst, - }, - ); - } - Err(_) => break, - } - } - - temps - } - - pub fn get_voltage(&self) -> Option { - let filename = self.hwmon_path.join("in0_input"); - - match fs::read_to_string(filename) { - Ok(voltage) => Some(voltage.trim().parse::().unwrap()), - Err(_) => None, - } - } - - pub fn get_power_cap_max(&self) -> Option { - let filename = self.hwmon_path.join("power1_cap_max"); - - match fs::read_to_string(filename) { - Ok(power_cap) => Some(power_cap.trim().parse::().unwrap() / 1000000), - _ => None, - } - } - - pub fn get_power_cap(&self) -> Option { - let filename = self.hwmon_path.join("power1_cap"); - - match fs::read_to_string(filename) { - Ok(a) => Some(a.trim().parse::().unwrap() / 1000000), - _ => None, - } - } - - pub fn set_power_cap(&mut self, cap: i64) -> Result<(), HWMonError> { - if cap - > self - .get_power_cap_max() - .ok_or_else(|| HWMonError::Unsupported)? - { - return Err(HWMonError::InvalidValue); - } - - let cap = cap * 1000000; - log::trace!("setting power cap to {}", cap); - - match fs::write(self.hwmon_path.join("power1_cap"), cap.to_string()) { - Ok(_) => Ok(()), - Err(_) => Err(HWMonError::PermissionDenied), - } - } - - pub fn get_power_avg(&self) -> Option { - let filename = self.hwmon_path.join("power1_average"); - - match fs::read_to_string(filename) { - Ok(a) => Some(a.trim().parse::().unwrap() / 1000000), - Err(_) => None, - } - } - - pub fn set_fan_curve(&self, curve: BTreeMap) { - log::trace!("trying to set curve"); - let mut current = self.fan_curve.write().unwrap(); - current.clear(); - - for (k, v) in curve.iter() { - current.insert(k.clone(), v.clone()); - } - log::trace!("set curve to {:?}", current); - } - - pub fn start_fan_control(&self) -> Result<(), HWMonError> { - if self.fan_control.load(Ordering::SeqCst) { - return Ok(()); - } - self.fan_control.store(true, Ordering::SeqCst); - - match fs::write(self.hwmon_path.join("pwm1_enable"), "1") { - Ok(_) => { - let s = self.clone(); - - thread::spawn(move || { - while s.fan_control.load(Ordering::SeqCst) { - let temps = s.get_temps(); - log::trace!("Temps: {:?}", temps); - - // Use junction temp when available, otherwise fall back to edge - let temps = match temps.get("junction") { - Some(temp) => temp, - None => temps.get("edge").unwrap(), - }; - - if temps.current >= temps.crit || temps.current <= temps.crit_hyst { - println!("CRITICAL TEMPERATURE DETECTED! FORCING MAX FAN SPEED"); - fs::write(s.hwmon_path.join("pwm1"), 255.to_string()) - .expect("Failed to set gpu temp in critical scenario (Warning: GPU Overheating!)"); - } - - log::trace!("Current gpu temp: {}", temps.current); - - let curve = s.fan_curve.read().unwrap(); - - for (t_low, s_low) in curve.iter() { - match curve.range(t_low..).nth(1) { - Some((t_high, s_high)) => { - if (t_low..t_high).contains(&&temps.current) { - let speed_ratio = (temps.current - t_low) as f64 - / (t_high - t_low) as f64; //The ratio of which speed to choose within the range of current lower and upper speeds - let speed_percent = - s_low + ((s_high - s_low) * speed_ratio); - let pwm = (255f64 * (speed_percent / 100f64)) as i64; - log::trace!("pwm: {}", pwm); - - fs::write(s.hwmon_path.join("pwm1"), pwm.to_string()) - .expect("Failed to write to pwm1"); - - log::trace!("In the range of {}..{}c {}..{}%, setting speed {}% ratio {}", t_low, t_high, s_low, s_high, speed_percent, speed_ratio); - break; - } - } - None => continue, - } - } - drop(curve); //needed to release rwlock so that the curve can be changed - - thread::sleep(Duration::from_millis(1000)); - } - }); - Ok(()) - } - Err(_) => Err(HWMonError::PermissionDenied), - } - } - - pub fn stop_fan_control(&self) -> Result<(), HWMonError> { - match fs::write(self.hwmon_path.join("pwm1_enable"), "2") { - Ok(_) => { - self.fan_control.store(false, Ordering::SeqCst); - log::trace!("Stopping fan control"); - Ok(()) - } - Err(_) => Err(HWMonError::PermissionDenied), - } - } - - pub fn get_fan_control(&self) -> (bool, BTreeMap) { - log::trace!("Fan control: {}", self.fan_control.load(Ordering::SeqCst)); - ( - self.fan_control.load(Ordering::SeqCst), - self.fan_curve.read().unwrap().clone(), - ) - } -} diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs deleted file mode 100644 index 13ed0d60..00000000 --- a/daemon/src/lib.rs +++ /dev/null @@ -1,498 +0,0 @@ -pub mod config; -pub mod daemon_connection; -pub mod gpu_controller; -pub mod hw_mon; - -use config::{Config, GpuConfig}; -use gpu_controller::PowerProfile; -use pciid_parser::Database; -use rand::prelude::*; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::{ - collections::{BTreeMap, HashMap}, - fs, -}; - -use crate::gpu_controller::GpuController; - -// Abstract socket allows anyone to connect without worrying about permissions -// https://unix.stackexchange.com/questions/579612/unix-domain-sockets-for-non-root-user -pub const SOCK_PATH: &str = "amdgpu-configurator.sock"; -pub const BUFFER_SIZE: usize = 16384; - -pub struct Daemon { - gpu_controllers: HashMap, - listener: std::os::unix::io::RawFd, - config: Config, -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum Action { - CheckAlive, - GetConfig, - SetConfig(Config), - GetGpus, - GetInfo(u32), - GetStats(u32), - StartFanControl(u32), - StopFanControl(u32), - GetFanControl(u32), - SetFanCurve(u32, BTreeMap), - SetPowerCap(u32, i64), - SetPowerProfile(u32, PowerProfile), - // SetGPUPowerState(u32, u32, i64, Option), - SetGPUMaxPowerState(u32, i64, Option), - SetVRAMMaxClock(u32, i64), - CommitGPUPowerStates(u32), - ResetGPUPowerStates(u32), - Shutdown, -} - -impl Daemon { - pub fn new(unprivileged: bool) -> Daemon { - let addr = nix::sys::socket::SockAddr::Unix( - nix::sys::socket::UnixAddr::new_abstract(SOCK_PATH.as_bytes()).unwrap(), - ); - let listener = nix::sys::socket::socket( - nix::sys::socket::AddressFamily::Unix, - nix::sys::socket::SockType::Stream, - nix::sys::socket::SockFlag::empty(), - None, - ) - .expect("Socket failed"); - nix::sys::socket::bind(listener, &addr).expect("Bind failed"); - nix::sys::socket::listen(listener, 128).expect("Listen failed"); - - let config_path = PathBuf::from("/etc/lact.json"); - let mut config = if unprivileged { - Config::new(&config_path) - } else { - match Config::read_from_file(&config_path) { - Ok(c) => { - log::info!("Loaded config from {}", c.config_path.to_string_lossy()); - c - } - Err(_) => { - log::info!("Config not found, creating"); - let c = Config::new(&config_path); - //c.save().unwrap(); - c - } - } - }; - - log::info!("Using config {:?}", config); - - let gpu_controllers = Self::load_gpu_controllers(&mut config); - - if !unprivileged { - config.save().unwrap(); - } - - Daemon { - listener, - gpu_controllers, - config, - } - } - - fn load_gpu_controllers(config: &mut Config) -> HashMap { - let pci_db = match config.allow_online_update { - Some(true) => match Database::get_online() { - Ok(db) => Some(db), - Err(e) => { - log::info!("Error updating PCI db: {:?}", e); - None - } - }, - Some(false) | None => None, - }; - - let mut gpu_controllers: HashMap = HashMap::new(); - - 'entries: for entry in - fs::read_dir("/sys/class/drm").expect("Could not open /sys/class/drm") - { - let entry = entry.unwrap(); - if entry.file_name().len() == 5 { - if entry.file_name().to_str().unwrap().split_at(4).0 == "card" { - log::info!("Initializing {:?}", entry.path()); - - let mut controller = - GpuController::new(entry.path().join("device"), GpuConfig::new(), &pci_db); - - let current_identifier = controller.get_identifier(); - - log::info!( - "Searching the config for GPU with identifier {:?}", - current_identifier - ); - - log::info!("{}", &config.gpu_configs.len()); - for (id, (gpu_identifier, gpu_config)) in &config.gpu_configs { - log::info!("Comparing with {:?}", gpu_identifier); - if current_identifier == *gpu_identifier { - controller.load_config(&gpu_config); - gpu_controllers.insert(id.clone(), controller); - log::info!("already known"); - continue 'entries; - } - - /*if gpu_info.pci_slot == gpu_identifier.pci_id - && gpu_info.vendor_data.card_model == gpu_identifier.card_model - && gpu_info.vendor_data.gpu_model == gpu_identifier.gpu_model - { - controller.load_config(&gpu_config); - gpu_controllers.insert(id.clone(), controller); - log::info!("already known"); - continue 'entries; - }*/ - } - - log::info!("initializing for the first time"); - - let id: u32 = random(); - - config - .gpu_configs - .insert(id, (controller.get_identifier(), controller.get_config())); - gpu_controllers.insert(id, controller); - } - } - } - - gpu_controllers - } - - pub fn listen(mut self) { - loop { - let stream = nix::sys::socket::accept(self.listener).expect("Accept failed"); - if stream < 0 { - log::error!("Error from accept"); - break; - } else { - Daemon::handle_connection(&mut self, stream); - } - } - } - - pub fn read_buffer(stream: i32) -> Vec { - log::trace!("Reading buffer"); - let mut buffer = Vec::::new(); - buffer.resize(BUFFER_SIZE, 0); - loop { - match nix::unistd::read(stream, &mut buffer) { - Ok(0) => { - break; - } - Ok(n) => { - assert!(n < buffer.len()); - if n < buffer.len() { - buffer.resize(n, 0); - } - break; - } - Err(e) => { - panic!("Error reading from socket: {}", e); - } - } - } - - buffer - } - - fn handle_connection(&mut self, stream: i32) { - let buffer = Self::read_buffer(stream); - - //log::trace!("finished reading, buffer size {}", buffer.len()); - log::trace!("Attempting to deserialize {:?}", &buffer); - //log::trace!("{:?}", action); - - match bincode::deserialize::(&buffer) { - Ok(action) => { - log::trace!("Executing action {:?}", action); - let response: Result = match action { - Action::CheckAlive => Ok(DaemonResponse::OK), - Action::GetGpus => { - let mut gpus: HashMap> = HashMap::new(); - for (id, controller) in &self.gpu_controllers { - gpus.insert(*id, controller.get_info().vendor_data.gpu_model.clone()); - } - Ok(DaemonResponse::Gpus(gpus)) - } - Action::GetStats(i) => match self.gpu_controllers.get(&i) { - Some(controller) => match controller.get_stats() { - Ok(stats) => Ok(DaemonResponse::GpuStats(stats)), - Err(_) => Err(DaemonError::HWMonError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::GetInfo(i) => match self.gpu_controllers.get(&i) { - Some(controller) => { - Ok(DaemonResponse::GpuInfo(controller.get_info().clone())) - } - None => Err(DaemonError::InvalidID), - }, - Action::StartFanControl(i) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.start_fan_control() { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::HWMonError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::StopFanControl(i) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.stop_fan_control() { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::HWMonError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::GetFanControl(i) => match self.gpu_controllers.get(&i) { - Some(controller) => match controller.get_fan_control() { - Ok(info) => Ok(DaemonResponse::FanControlInfo(info)), - Err(_) => Err(DaemonError::HWMonError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::SetFanCurve(i, curve) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.set_fan_curve(curve) { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::HWMonError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::SetPowerCap(i, cap) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.set_power_cap(cap) { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::HWMonError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::SetPowerProfile(i, profile) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.set_power_profile(profile) { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::ControllerError), - }, - None => Err(DaemonError::InvalidID), - }, - /*Action::SetGPUPowerState(i, num, clockspeed, voltage) => { - match self.gpu_controllers.get_mut(&i) { - Some(controller) => { - match controller.set_gpu_power_state(num, clockspeed, voltage) { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::ControllerError), - } - } - None => Err(DaemonError::InvalidID), - } - }*/ - Action::SetGPUMaxPowerState(i, clockspeed, voltage) => { - match self.gpu_controllers.get_mut(&i) { - Some(controller) => { - match controller.set_gpu_max_power_state(clockspeed, voltage) { - Ok(()) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::ControllerError), - } - } - None => Err(DaemonError::InvalidID), - } - } - Action::SetVRAMMaxClock(i, clockspeed) => { - match self.gpu_controllers.get_mut(&i) { - Some(controller) => { - match controller.set_vram_max_clockspeed(clockspeed) { - Ok(()) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::ControllerError), - } - } - None => Err(DaemonError::InvalidID), - } - } - Action::CommitGPUPowerStates(i) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.commit_gpu_power_states() { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::ControllerError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::ResetGPUPowerStates(i) => match self.gpu_controllers.get_mut(&i) { - Some(controller) => match controller.reset_gpu_power_states() { - Ok(_) => { - self.config.gpu_configs.insert( - i, - (controller.get_identifier(), controller.get_config()), - ); - self.config.save().unwrap(); - Ok(DaemonResponse::OK) - } - Err(_) => Err(DaemonError::ControllerError), - }, - None => Err(DaemonError::InvalidID), - }, - Action::Shutdown => { - for (id, controller) in &mut self.gpu_controllers { - #[allow(unused_must_use)] - { - controller.reset_gpu_power_states(); - controller.commit_gpu_power_states(); - controller.set_power_profile(PowerProfile::Auto); - - if self - .config - .gpu_configs - .get(id) - .unwrap() - .1 - .fan_control_enabled - { - controller.stop_fan_control(); - } - } - } - std::process::exit(0); - } - Action::SetConfig(config) => { - self.config = config; - self.gpu_controllers.clear(); - self.gpu_controllers = Self::load_gpu_controllers(&mut self.config); - self.config.save().expect("Failed to save config"); - Ok(DaemonResponse::OK) - } - Action::GetConfig => Ok(DaemonResponse::Config(self.config.clone())), - }; - - let buffer = bincode::serialize(&response).unwrap(); - - log::trace!("Responding, buffer length {}", buffer.len()); - nix::unistd::write(stream, &buffer).expect("Writing response to socket failed"); - - nix::sys::socket::shutdown(stream, nix::sys::socket::Shutdown::Both) - .expect("Failed to shut down"); - nix::unistd::close(stream).expect("Failed to close"); - - log::trace!("Finished responding"); - } - Err(_) => { - println!("Failed deserializing action"); - } - } - } -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum DaemonResponse { - OK, - GpuInfo(gpu_controller::GpuInfo), - GpuStats(gpu_controller::GpuStats), - Gpus(HashMap>), - PowerCap((i64, i64)), - FanControlInfo(gpu_controller::FanControlInfo), - Config(Config), -} - -#[derive(Serialize, Deserialize, Debug)] -pub enum DaemonError { - ConnectionFailed, - InvalidID, - HWMonError, - ControllerError, -} - -#[cfg(test)] -mod tests { - use crate::gpu_controller::VendorData; - - use super::*; - - fn init() { - let _ = env_logger::builder().is_test(true).try_init(); - } - - #[test] - fn recognize_polaris() { - init(); - - let db = Database::get_online().unwrap(); - - let vendor_data: VendorData = db.get_device_info("1002", "67df", "1da2", "e387").into(); - - assert_eq!( - vendor_data.gpu_vendor, - Some("Advanced Micro Devices, Inc. [AMD/ATI]".to_string()) - ); - - assert_eq!( - vendor_data.gpu_model, - Some("Ellesmere [Radeon RX 470/480/570/570X/580/580X/590]".to_string()) - ); - - assert_eq!( - vendor_data.card_model, - Some("Radeon RX 580 Pulse 4GB".to_string()) - ); - } -} diff --git a/daemon/src/main.rs b/daemon/src/main.rs deleted file mode 100644 index ed17e1fe..00000000 --- a/daemon/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use std::thread; - -use daemon::{daemon_connection::DaemonConnection, Daemon}; -use signal_hook::consts::{SIGINT, SIGTERM}; -use signal_hook::iterator::Signals; - -fn main() { - env_logger::init(); - let d = Daemon::new(false); - let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); - - thread::spawn(move || { - for _ in signals.forever() { - log::info!("Shutting down"); - let d = DaemonConnection::new().unwrap(); - d.shutdown(); - } - }); - - d.listen(); -} diff --git a/deb/DEBIAN/conffiles b/deb/DEBIAN/conffiles deleted file mode 100644 index e69de29b..00000000 diff --git a/deb/DEBIAN/control b/deb/DEBIAN/control deleted file mode 100644 index 21ea5bc0..00000000 --- a/deb/DEBIAN/control +++ /dev/null @@ -1,9 +0,0 @@ -Package: lact -Version: 0.1 -Architecture: all -Essential: no -Section: admin -Priority: optional -Depends: libgtk-3-0 -Maintainer: Ilya Zlobintsev -Description: AMDGPU Control Utility diff --git a/deb/usr/share/applications/lact.desktop b/deb/usr/share/applications/lact.desktop deleted file mode 100644 index 532759fa..00000000 --- a/deb/usr/share/applications/lact.desktop +++ /dev/null @@ -1,5 +0,0 @@ -[Desktop Entry] -Type=Application -Name=LACT -Description=AMDGPU Control Application -Exec=lact-gui \ No newline at end of file diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index c21196a4..00000000 --- a/deploy.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -cargo build --release && -sudo install -Dm755 target/release/daemon /usr/local/bin/lact-daemon && -sudo install -Dm755 target/release/gui /usr/local/bin/lact-gui && -sudo install -Dm755 target/release/cli /usr/local/bin/lact-cli && -sudo install -Dm644 lactd.service /etc/systemd/system/lactd.service && -sudo mkdir -p /usr/local/share/applications && -sudo install -Dm644 lact.desktop /usr/local/share/applications/ && -sudo systemctl daemon-reload -sudo systemctl enable lactd && -sudo systemctl restart lactd diff --git a/gui/Cargo.toml b/gui/Cargo.toml deleted file mode 100644 index 5116b30d..00000000 --- a/gui/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "gui" -version = "0.1.0" -authors = ["Ilya Zlobintsev "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -daemon = { path = "../daemon" } - -gtk = { version = "0.14", features = ["v3_22"] } -pango = "0.14" -glib = "0.14" - -log = "0.4" -env_logger = "0.9" \ No newline at end of file diff --git a/gui/src/app.rs b/gui/src/app.rs deleted file mode 100644 index ddcb2ce6..00000000 --- a/gui/src/app.rs +++ /dev/null @@ -1,296 +0,0 @@ -mod apply_revealer; -mod header; -mod root_stack; - -extern crate gtk; - -use std::sync::Arc; -use std::thread; -use std::time::Duration; -use std::{ - fs, - sync::atomic::{AtomicU32, Ordering}, -}; - -use apply_revealer::ApplyRevealer; -use daemon::daemon_connection::DaemonConnection; -use daemon::gpu_controller::GpuStats; -use daemon::DaemonError; -use gtk::prelude::*; -use gtk::*; - -use header::Header; -use root_stack::RootStack; - -#[derive(Clone)] -pub struct App { - pub window: Window, - pub header: Header, - root_stack: RootStack, - apply_revealer: ApplyRevealer, - daemon_connection: DaemonConnection, -} - -impl App { - pub fn new(daemon_connection: DaemonConnection) -> Self { - let window = Window::new(WindowType::Toplevel); - - let header = Header::new(); - - window.set_titlebar(Some(&header.container)); - window.set_title("LACT"); - - window.set_default_size(500, 600); - - window.connect_delete_event(move |_, _| { - main_quit(); - Inhibit(false) - }); - - let root_stack = RootStack::new(); - - header.set_switcher_stack(&root_stack.container); - - let root_box = Box::new(Orientation::Vertical, 5); - - root_box.add(&root_stack.container); - - let apply_revealer = ApplyRevealer::new(); - - root_box.add(&apply_revealer.container); - - window.add(&root_box); - - App { - window, - header, - root_stack, - apply_revealer, - daemon_connection, - } - } - - pub fn run(&self) -> Result<(), DaemonError> { - self.window.show_all(); - - let current_gpu_id = Arc::new(AtomicU32::new(0)); - - { - let current_gpu_id = current_gpu_id.clone(); - let app = self.clone(); - - self.header.connect_gpu_selection_changed(move |gpu_id| { - log::info!("GPU Selection changed"); - app.set_info(gpu_id); - current_gpu_id.store(gpu_id, Ordering::SeqCst); - }); - } - - let gpus = self.daemon_connection.get_gpus()?; - - self.header.set_gpus(gpus); - - // Show apply button on setting changes - { - let apply_revealer = self.apply_revealer.clone(); - - self.root_stack - .thermals_page - .connect_settings_changed(move || { - log::info!("Settings changed, showing apply button"); - apply_revealer.show(); - }); - - let apply_revealer = self.apply_revealer.clone(); - - self.root_stack.oc_page.connect_settings_changed(move || { - log::info!("Settings changed, showing apply button"); - apply_revealer.show(); - }); - } - - { - let app = self.clone(); - let current_gpu_id = current_gpu_id.clone(); - - self.root_stack.oc_page.connect_clocks_reset(move || { - log::info!("Resetting clocks, but not applying"); - - let gpu_id = current_gpu_id.load(Ordering::SeqCst); - - app.daemon_connection - .reset_gpu_power_states(gpu_id) - .expect("Failed to reset clocks"); - - app.set_info(gpu_id); - - app.apply_revealer.show(); - }) - } - - // Apply settings - { - let current_gpu_id = current_gpu_id.clone(); - let app = self.clone(); - - self.apply_revealer.connect_apply_button_clicked(move || { - log::info!("Applying settings"); - - let gpu_id = current_gpu_id.load(Ordering::SeqCst); - - { - let thermals_settings = app.root_stack.thermals_page.get_thermals_settings(); - - if thermals_settings.automatic_fan_control_enabled { - app.daemon_connection - .stop_fan_control(gpu_id) - .unwrap_or(println!("Failed to stop fan control")); - } else { - app.daemon_connection - .start_fan_control(gpu_id) - .unwrap_or(println!("Failed to start fan control")); - } - - app.daemon_connection - .set_fan_curve(gpu_id, thermals_settings.curve) - .unwrap_or(println!("Failed to set fan curve")); - } - - if let Some(clocks_settings) = app.root_stack.oc_page.get_clocks() { - app.daemon_connection - .set_gpu_max_power_state( - gpu_id, - clocks_settings.gpu_clock, - Some(clocks_settings.gpu_voltage), - ) - .expect("Failed to set GPU clockspeed/voltage"); - - app.daemon_connection - .set_vram_max_clock(gpu_id, clocks_settings.vram_clock) - .expect("Failed to set VRAM Clock"); - - app.daemon_connection - .commit_gpu_power_states(gpu_id) - .expect("Failed to commit power states"); - } - - if let Some(profile) = app.root_stack.oc_page.get_power_profile() { - app.daemon_connection - .set_power_profile(gpu_id, profile) - .expect("Failed to set power profile"); - } - - if let Some(cap) = app.root_stack.oc_page.get_power_cap() { - app.daemon_connection - .set_power_cap(gpu_id, cap) - .expect("Failed to set power cap"); - } - - app.set_info(gpu_id); - }); - } - - self.start_stats_update_loop(current_gpu_id.clone()); - - Ok(gtk::main()) - } - - fn set_info(&self, gpu_id: u32) { - let gpu_info = self.daemon_connection.get_gpu_info(gpu_id).unwrap(); - log::trace!("Setting info {:?}", &gpu_info); - - self.root_stack.info_page.set_info(&gpu_info); - - log::trace!("Setting clocks"); - self.root_stack.oc_page.set_info(&gpu_info); - - log::trace!("Setting power profile {:?}", gpu_info.power_profile); - self.root_stack - .oc_page - .set_power_profile(&gpu_info.power_profile); - - log::trace!("Setting fan control info"); - match self.daemon_connection.get_fan_control(gpu_id) { - Ok(fan_control_info) => self - .root_stack - .thermals_page - .set_ventilation_info(fan_control_info), - Err(_) => self.root_stack.thermals_page.hide_fan_controls(), - } - - { - // It's overkill to both show and hide the frame, but it needs to be done in set_info because show_all overrides the default hidden state of the frame. - match fs::read_to_string("/sys/module/amdgpu/parameters/ppfeaturemask") { - Ok(ppfeaturemask) => { - const PP_OVERDRIVE_MASK: i32 = 0x4000; - - let ppfeaturemask = ppfeaturemask.trim().strip_prefix("0x").unwrap(); - - log::trace!("ppfeaturemask {}", ppfeaturemask); - - let ppfeaturemask: u64 = - u64::from_str_radix(ppfeaturemask, 16).expect("Invalid ppfeaturemask"); - - if (ppfeaturemask & PP_OVERDRIVE_MASK as u64) > 0 { - self.root_stack.oc_page.warning_frame.hide(); - } else { - self.root_stack.oc_page.warning_frame.show(); - } - } - Err(_) => { - log::info!("Failed to read feature mask! This is expected if your system doesn't have an AMD GPU."); - self.root_stack.oc_page.warning_frame.hide(); - } - } - } - - self.apply_revealer.hide(); - } - - fn start_stats_update_loop(&self, current_gpu_id: Arc) { - let context = glib::MainContext::default(); - - let _guard = context.acquire(); - - // The loop that gets stats - let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); - { - let daemon_connection = self.daemon_connection.clone(); - - thread::spawn(move || loop { - let gpu_id = current_gpu_id.load(Ordering::SeqCst); - - if let Ok(stats) = daemon_connection.get_gpu_stats(gpu_id) { - sender.send(GuiUpdateMsg::GpuStats(stats)).unwrap(); - } - - thread::sleep(Duration::from_millis(500)); - }); - } - - // Receiving stats into the gui event loop - { - let thermals_page = self.root_stack.thermals_page.clone(); - let oc_page = self.root_stack.oc_page.clone(); - - receiver.attach(None, move |msg| { - match msg { - GuiUpdateMsg::GpuStats(stats) => { - log::trace!("New stats received, updating"); - thermals_page.set_thermals_info(&stats); - oc_page.set_stats(&stats); - } /*GuiUpdateMsg::FanControlInfo(fan_control_info) => { - thermals_page.set_ventilation_info(fan_control_info) - }*/ - } - - glib::Continue(true) - }); - } - } -} - -enum GuiUpdateMsg { - // FanControlInfo(FanControlInfo), - GpuStats(GpuStats), -} diff --git a/gui/src/app/apply_revealer.rs b/gui/src/app/apply_revealer.rs deleted file mode 100644 index b940a690..00000000 --- a/gui/src/app/apply_revealer.rs +++ /dev/null @@ -1,41 +0,0 @@ -use gtk::prelude::*; -use gtk::*; - -#[derive(Clone)] -pub struct ApplyRevealer { - pub container: Revealer, - apply_button: Button, -} - -impl ApplyRevealer { - pub fn new() -> Self { - let container = Revealer::new(); - - container.set_transition_duration(150); - - let apply_button = Button::new(); - - apply_button.set_label("Apply"); - - container.add(&apply_button); - - Self { - container, - apply_button, - } - } - - pub fn show(&self) { - self.container.set_reveal_child(true); - } - - pub fn hide(&self) { - self.container.set_reveal_child(false); - } - - pub fn connect_apply_button_clicked(&self, f: F) { - self.apply_button.connect_clicked(move |_| { - f(); - }); - } -} diff --git a/gui/src/app/root_stack.rs b/gui/src/app/root_stack.rs deleted file mode 100644 index 60bd65f4..00000000 --- a/gui/src/app/root_stack.rs +++ /dev/null @@ -1,51 +0,0 @@ -mod info_page; -mod oc_page; -mod software_page; -mod thermals_page; - -use gtk::prelude::*; -use gtk::*; - -use info_page::InformationPage; -use oc_page::OcPage; -use software_page::SoftwarePage; -use thermals_page::ThermalsPage; - -#[derive(Clone)] -pub struct RootStack { - pub container: Stack, - pub info_page: InformationPage, - pub thermals_page: ThermalsPage, - pub software_page: SoftwarePage, - pub oc_page: OcPage, -} - -impl RootStack { - pub fn new() -> Self { - let container = Stack::new(); - - let info_page = InformationPage::new(); - - container.add_titled(&info_page.container, "info_page", "Information"); - - let oc_page = OcPage::new(); - - container.add_titled(&oc_page.container, "oc_page", "OC"); - - let thermals_page = ThermalsPage::new(); - - container.add_titled(&thermals_page.container, "thermals_page", "Thermals"); - - let software_page = SoftwarePage::new(); - - container.add_titled(&software_page.container, "software_page", "Software"); - - Self { - container, - info_page, - thermals_page, - oc_page, - software_page, - } - } -} diff --git a/gui/src/app/root_stack/info_page/vulkan_info.rs b/gui/src/app/root_stack/info_page/vulkan_info.rs deleted file mode 100644 index 5f6f4cc1..00000000 --- a/gui/src/app/root_stack/info_page/vulkan_info.rs +++ /dev/null @@ -1,123 +0,0 @@ -use daemon::gpu_controller::VulkanInfo; -use gtk::prelude::*; -use gtk::*; - -#[derive(Clone)] -pub struct VulkanInfoFrame { - pub container: Frame, - device_name_label: Label, - version_label: Label, - features_box: Box, -} - -impl VulkanInfoFrame { - pub fn new() -> Self { - let container = Frame::new(None); - - container.set_label_widget(Some(&{ - let label = Label::new(None); - label.set_markup("Vulkan Information"); - label - })); - container.set_label_align(0.5, 0.5); - - container.set_shadow_type(ShadowType::None); - - let grid = Grid::new(); - - grid.set_margin_start(5); - grid.set_margin_end(5); - grid.set_margin_bottom(5); - grid.set_margin_top(5); - - grid.set_column_homogeneous(true); - - grid.set_row_spacing(7); - grid.set_column_spacing(5); - - grid.attach( - &{ - let label = Label::new(Some("Device name:")); - label.set_halign(Align::End); - label - }, - 0, - 0, - 2, - 1, - ); - - let device_name_label = Label::new(None); - device_name_label.set_halign(Align::Start); - - grid.attach(&device_name_label, 2, 0, 3, 1); - - grid.attach( - &{ - let label = Label::new(Some("Version:")); - label.set_halign(Align::End); - label - }, - 0, - 1, - 2, - 1, - ); - - let version_label = Label::new(None); - version_label.set_halign(Align::Start); - - grid.attach(&version_label, 2, 1, 3, 1); - - let features_expander = Expander::new(Some("Feature support")); - - grid.attach(&features_expander, 0, 2, 5, 1); - - let features_scrolled_window = ScrolledWindow::new(NONE_ADJUSTMENT, NONE_ADJUSTMENT); - - features_scrolled_window.set_vexpand(true); - - let features_box = Box::new(Orientation::Vertical, 5); - - features_box.set_halign(Align::Center); - - features_scrolled_window.add(&features_box); - - features_expander.add(&features_scrolled_window); - - container.add(&grid); - - Self { - container, - device_name_label, - version_label, - features_box, - } - } - - pub fn set_info(&self, vulkan_info: &VulkanInfo) { - log::trace!("Setting vulkan info: {:?}", vulkan_info); - - self.device_name_label - .set_markup(&format!("{}", vulkan_info.device_name)); - self.version_label - .set_markup(&format!("{}", vulkan_info.api_version)); - - for (feature, supported) in vulkan_info.features.iter() { - let vbox = Box::new(Orientation::Horizontal, 5); - - let feature_name_label = Label::new(Some(feature)); - - vbox.pack_start(&feature_name_label, false, false, 0); - - let feature_supported_checkbutton = CheckButton::new(); - - feature_supported_checkbutton.set_sensitive(false); - feature_supported_checkbutton.set_active(*supported); - - vbox.pack_start(&feature_supported_checkbutton, false, false, 0); - - self.features_box.pack_end(&vbox, false, false, 0); - } - } -} diff --git a/gui/src/app/root_stack/oc_page.rs b/gui/src/app/root_stack/oc_page.rs deleted file mode 100644 index 1ce6be94..00000000 --- a/gui/src/app/root_stack/oc_page.rs +++ /dev/null @@ -1,133 +0,0 @@ -mod clocks_frame; -mod power_cap_frame; -mod power_profile_frame; -mod stats_grid; -mod warning_frame; - -use clocks_frame::ClocksSettings; -use daemon::gpu_controller::{GpuInfo, GpuStats, PowerProfile}; -use gtk::prelude::*; -use gtk::*; - -use clocks_frame::ClocksFrame; -use power_cap_frame::PowerCapFrame; -use power_profile_frame::PowerProfileFrame; -use stats_grid::StatsGrid; -use warning_frame::WarningFrame; - -#[derive(Clone)] -pub struct OcPage { - pub container: Box, - stats_grid: StatsGrid, - power_profile_frame: PowerProfileFrame, - power_cap_frame: PowerCapFrame, - clocks_frame: ClocksFrame, - pub warning_frame: WarningFrame, -} - -impl OcPage { - pub fn new() -> Self { - let container = Box::new(Orientation::Vertical, 5); - - let warning_frame = WarningFrame::new(); - - container.pack_start(&warning_frame.container, false, true, 5); - - let stats_grid = StatsGrid::new(); - - container.pack_start(&stats_grid.container, false, true, 5); - - let power_cap_frame = PowerCapFrame::new(); - - container.pack_start(&power_cap_frame.container, false, true, 0); - - let power_profile_frame = PowerProfileFrame::new(); - - container.pack_start(&power_profile_frame.container, false, true, 0); - - let clocks_frame = ClocksFrame::new(); - - container.pack_start(&clocks_frame.container, false, true, 0); - - Self { - container, - stats_grid, - power_profile_frame, - clocks_frame, - warning_frame, - power_cap_frame, - } - } - - pub fn set_stats(&self, stats: &GpuStats) { - self.stats_grid.set_stats(stats); - } - - pub fn connect_clocks_reset(&self, f: F) { - self.clocks_frame.connect_clocks_reset(move || { - f(); - }); - } - - pub fn connect_settings_changed(&self, f: F) { - { - let f = f.clone(); - self.power_profile_frame - .connect_power_profile_changed(move || { - f(); - }); - } - { - let f = f.clone(); - self.clocks_frame.connect_clocks_changed(move || { - f(); - }) - } - { - self.power_cap_frame.connect_cap_changed(move || { - f(); - }) - } - } - - pub fn set_power_profile(&self, profile: &Option) { - match profile { - Some(profile) => { - self.power_profile_frame.show(); - self.power_profile_frame.set_active_profile(profile); - } - None => self.power_profile_frame.hide(), - } - } - - pub fn get_power_profile(&self) -> Option { - match self.power_profile_frame.get_visibility() { - true => Some(self.power_profile_frame.get_selected_power_profile()), - false => None, - } - } - - pub fn set_info(&self, info: &GpuInfo) { - match &info.clocks_table { - Some(clocks_table) => { - self.clocks_frame.show(); - self.clocks_frame.set_clocks(clocks_table); - } - None => self.clocks_frame.hide(), - } - - self.power_cap_frame - .set_data(info.power_cap, info.power_cap_max); - } - - pub fn get_clocks(&self) -> Option { - match self.clocks_frame.get_visibility() { - true => Some(self.clocks_frame.get_settings()), - false => None, - } - } - - pub fn get_power_cap(&self) -> Option { - self.power_cap_frame.get_cap() - } -} diff --git a/gui/src/app/root_stack/oc_page/clocks_frame.rs b/gui/src/app/root_stack/oc_page/clocks_frame.rs deleted file mode 100644 index 6605b128..00000000 --- a/gui/src/app/root_stack/oc_page/clocks_frame.rs +++ /dev/null @@ -1,228 +0,0 @@ -use daemon::gpu_controller::ClocksTable; -use gtk::prelude::*; -use gtk::*; - -pub struct ClocksSettings { - pub gpu_clock: i64, - pub vram_clock: i64, - pub gpu_voltage: i64, -} - -#[derive(Clone)] -pub struct ClocksFrame { - pub container: Frame, - gpu_clock_adjustment: Adjustment, - gpu_voltage_adjustment: Adjustment, - vram_clock_adjustment: Adjustment, - apply_button: Button, -} - -impl ClocksFrame { - pub fn new() -> Self { - let container = Frame::new(None); - - container.set_margin_start(10); - container.set_margin_end(10); - - container.set_shadow_type(ShadowType::None); - - container.set_label_widget(Some(&{ - let label = Label::new(None); - label.set_markup("Maximum Clocks"); - label - })); - container.set_label_align(0.2, 0.0); - - let gpu_clock_adjustment = Adjustment::new(0.0, 0.0, 0.0, 1.0, 0.0, 0.0); - - let gpu_voltage_adjustment = Adjustment::new(1.0, 0.0, 0.0, 0.05, 0.0, 0.0); - - let vram_clock_adjustment = Adjustment::new(0.0, 0.0, 0.0, 1.0, 0.0, 0.0); - - let root_grid = Grid::new(); - - root_grid.set_row_spacing(5); - root_grid.set_column_spacing(10); - - { - let gpu_clock_scale = Scale::new(Orientation::Horizontal, Some(&gpu_clock_adjustment)); - - gpu_clock_scale.set_hexpand(true); // Affects the grid column and all scales - - gpu_clock_scale.set_value_pos(PositionType::Right); - - root_grid.attach(&gpu_clock_scale, 1, 0, 1, 1); - - root_grid.attach_next_to( - &Label::new(Some("GPU Clock (MHz)")), - Some(&gpu_clock_scale), - PositionType::Left, - 1, - 1, - ); - - let gpu_voltage_scale = - Scale::new(Orientation::Horizontal, Some(&gpu_voltage_adjustment)); - - gpu_voltage_scale.set_value_pos(PositionType::Right); - - gpu_voltage_scale.set_digits(3); - gpu_voltage_scale.set_round_digits(3); - - root_grid.attach(&gpu_voltage_scale, 1, 1, 1, 1); - - root_grid.attach_next_to( - &Label::new(Some("GPU Voltage (V)")), - Some(&gpu_voltage_scale), - PositionType::Left, - 1, - 1, - ); - - let vram_clock_scale = - Scale::new(Orientation::Horizontal, Some(&vram_clock_adjustment)); - - vram_clock_scale.set_value_pos(PositionType::Right); - - root_grid.attach(&vram_clock_scale, 1, 2, 1, 1); - - root_grid.attach_next_to( - &Label::new(Some("VRAM Clock (MHz)")), - Some(&vram_clock_scale), - PositionType::Left, - 1, - 1, - ); - } - - let apply_button = Button::new(); - - { - apply_button.set_label("Reset"); - - root_grid.attach(&apply_button, 0, 3, 2, 1); - - container.add(&root_grid); - } - - Self { - container, - gpu_clock_adjustment, - gpu_voltage_adjustment, - vram_clock_adjustment, - apply_button, - } - } - - pub fn get_visibility(&self) -> bool { - self.container.get_visible() - } - - pub fn set_clocks(&self, clocks_table: &ClocksTable) { - match clocks_table { - ClocksTable::Old(clocks_table) => { - self.gpu_clock_adjustment - .set_lower(clocks_table.gpu_clocks_range.0 as f64); - self.gpu_clock_adjustment - .set_upper(clocks_table.gpu_clocks_range.1 as f64); - - self.gpu_voltage_adjustment - .set_lower(clocks_table.voltage_range.0 as f64 / 1000.0); - self.gpu_voltage_adjustment - .set_upper(clocks_table.voltage_range.1 as f64 / 1000.0); - - self.vram_clock_adjustment - .set_lower(clocks_table.mem_clocks_range.0 as f64); - self.vram_clock_adjustment - .set_upper(clocks_table.mem_clocks_range.1 as f64); - - let (gpu_clockspeed, gpu_voltage) = - clocks_table.gpu_power_levels.iter().next_back().unwrap().1; - - self.gpu_clock_adjustment.set_value(*gpu_clockspeed as f64); - - self.gpu_voltage_adjustment - .set_value(*gpu_voltage as f64 / 1000.0); - - let (vram_clockspeed, _) = - clocks_table.mem_power_levels.iter().next_back().unwrap().1; - - self.vram_clock_adjustment - .set_value(*vram_clockspeed as f64); - } - ClocksTable::New(clocks_table) => { - self.gpu_clock_adjustment - .set_lower(clocks_table.gpu_clocks_range.0 as f64); - self.gpu_clock_adjustment - .set_upper(clocks_table.gpu_clocks_range.1 as f64); - - /* self.gpu_voltage_adjustment - .set_lower(clocks_table.voltage_range.0 as f64 / 1000.0); - self.gpu_voltage_adjustment - .set_upper(clocks_table.voltage_range.1 as f64 / 1000.0);*/ - - self.vram_clock_adjustment - .set_lower(clocks_table.mem_clocks_range.0 as f64); - self.vram_clock_adjustment - .set_upper(clocks_table.mem_clocks_range.1 as f64); - - self.gpu_clock_adjustment - .set_value(clocks_table.current_gpu_clocks.1 as f64); - - // self.gpu_voltage_adjustment - // .set_value(*clocks_table.gpu_voltage as f64 / 1000.0); - - self.vram_clock_adjustment - .set_value(clocks_table.current_max_mem_clock as f64); - } - } - } - - pub fn get_settings(&self) -> ClocksSettings { - let gpu_clock = self.gpu_clock_adjustment.value() as i64; - - let vram_clock = self.vram_clock_adjustment.value() as i64; - - let gpu_voltage = (self.gpu_voltage_adjustment.value() * 1000.0) as i64; - - ClocksSettings { - gpu_clock, - vram_clock, - gpu_voltage, - } - } - - pub fn connect_clocks_reset(&self, f: F) { - self.apply_button.connect_clicked(move |_| { - f(); - }); - } - - pub fn connect_clocks_changed(&self, f: F) { - { - let f = f.clone(); - self.gpu_clock_adjustment.connect_value_changed(move |_| { - f(); - }); - } - { - let f = f.clone(); - self.vram_clock_adjustment.connect_value_changed(move |_| { - f(); - }); - } - { - self.gpu_voltage_adjustment.connect_value_changed(move |_| { - f(); - }); - } - } - - pub fn hide(&self) { - self.container.set_visible(false); - } - - pub fn show(&self) { - self.container.set_visible(true); - } -} diff --git a/gui/src/app/root_stack/oc_page/power_cap_frame.rs b/gui/src/app/root_stack/oc_page/power_cap_frame.rs deleted file mode 100644 index 683eecb4..00000000 --- a/gui/src/app/root_stack/oc_page/power_cap_frame.rs +++ /dev/null @@ -1,81 +0,0 @@ -use gtk::prelude::*; -use gtk::*; - -#[derive(Clone)] -pub struct PowerCapFrame { - pub container: Frame, - label: Label, - adjustment: Adjustment, -} - -impl PowerCapFrame { - pub fn new() -> Self { - let container = Frame::new(None); - - container.set_shadow_type(ShadowType::None); - - container.set_label_widget(Some(&{ - let label = Label::new(None); - label.set_markup("Power Usage Limit"); - label - })); - container.set_label_align(0.2, 0.0); - - let root_box = Box::new(Orientation::Horizontal, 0); - - let label = Label::new(None); - - root_box.pack_start(&label, false, true, 5); - - let adjustment = Adjustment::new(0.0, 0.0, 0.0, 1.0, 10.0, 0.0); - { - let label = label.clone(); - adjustment.connect_value_changed(move |adj| { - label.set_markup(&format!("{}/{} W", adj.value().round(), adj.upper())); - }); - } - - let scale = Scale::new(Orientation::Horizontal, Some(&adjustment)); - - scale.set_draw_value(false); - - root_box.pack_start(&scale, true, true, 5); - - container.add(&root_box); - - Self { - container, - label, - adjustment, - } - } - - pub fn set_data(&self, power_cap: Option, power_cap_max: Option) { - if let Some(power_cap_max) = power_cap_max { - self.adjustment.set_upper(power_cap_max as f64); - } else { - self.container.set_visible(false); - } - if let Some(power_cap) = power_cap { - self.adjustment.set_value(power_cap as f64); - } else { - self.container.set_visible(false); - } - } - - pub fn get_cap(&self) -> Option { - // Using match gives a warning that floats shouldn't be used in patterns - let cap = self.adjustment.value(); - if cap == 0.0 { - None - } else { - Some(cap as i64) - } - } - - pub fn connect_cap_changed(&self, f: F) { - self.adjustment.connect_value_changed(move |_| { - f(); - }); - } -} diff --git a/gui/src/app/root_stack/oc_page/power_profile_frame.rs b/gui/src/app/root_stack/oc_page/power_profile_frame.rs deleted file mode 100644 index 43a054f3..00000000 --- a/gui/src/app/root_stack/oc_page/power_profile_frame.rs +++ /dev/null @@ -1,94 +0,0 @@ -use daemon::gpu_controller::PowerProfile; -use gtk::prelude::*; -use gtk::*; - -#[derive(Clone)] -pub struct PowerProfileFrame { - pub container: Frame, - combo_box: ComboBoxText, - description_label: Label, -} - -impl PowerProfileFrame { - pub fn new() -> Self { - let container = Frame::new(None); - - container.set_shadow_type(ShadowType::None); - - container.set_label_widget(Some(&{ - let label = Label::new(None); - label.set_markup("Power Profile"); - label - })); - container.set_label_align(0.2, 0.0); - - let root_box = Box::new(Orientation::Horizontal, 5); - - let combo_box = ComboBoxText::new(); - - combo_box.append(Some("0"), "Automatic"); - combo_box.append(Some("1"), "Highest clocks"); - combo_box.append(Some("2"), "Lowest clocks"); - - root_box.pack_start(&combo_box, false, true, 5); - - let description_label = Label::new(Some("A description is supposed to be here")); - - root_box.pack_start(&description_label, false, true, 5); - - { - let description_label = description_label.clone(); - combo_box.connect_changed(move |combobox| match combobox.active().unwrap() { - 0 => description_label - .set_text("Automatically adjust GPU and VRAM clocks. (Default)"), - 1 => description_label - .set_text("Always use the highest clockspeeds for GPU and VRAM."), - 2 => description_label - .set_text("Always use the lowest clockspeeds for GPU and VRAM."), - _ => unreachable!(), - }); - } - - container.add(&root_box); - Self { - container, - combo_box, - description_label, - } - } - - pub fn set_active_profile(&self, profile: &PowerProfile) { - match profile { - PowerProfile::Auto => self.combo_box.set_active_id(Some("0")), - PowerProfile::High => self.combo_box.set_active_id(Some("1")), - PowerProfile::Low => self.combo_box.set_active_id(Some("2")), - }; - } - - pub fn connect_power_profile_changed(&self, f: F) { - self.combo_box.connect_changed(move |_| { - f(); - }); - } - - pub fn get_selected_power_profile(&self) -> PowerProfile { - match self.combo_box.active().unwrap() { - 0 => PowerProfile::Auto, - 1 => PowerProfile::High, - 2 => PowerProfile::Low, - _ => unreachable!(), - } - } - - pub fn show(&self) { - self.container.set_visible(true); - } - - pub fn hide(&self) { - self.container.set_visible(false); - } - - pub fn get_visibility(&self) -> bool { - self.container.get_visible() - } -} diff --git a/gui/src/app/root_stack/oc_page/warning_frame.rs b/gui/src/app/root_stack/oc_page/warning_frame.rs deleted file mode 100644 index 70a3456e..00000000 --- a/gui/src/app/root_stack/oc_page/warning_frame.rs +++ /dev/null @@ -1,33 +0,0 @@ -use gtk::prelude::*; -use gtk::*; - -#[derive(Clone)] -pub struct WarningFrame { - pub container: Frame, -} - -impl WarningFrame { - pub fn new() -> Self { - let container = Frame::new(Some("Overclocking information")); - - container.set_label_align(0.3, 0.5); - - let warning_label = Label::new(None); - - warning_label.set_line_wrap(true); - warning_label.set_markup("Overclocking support is not enabled! To enable overclocking support, you need to add amdgpu.ppfeaturemask=0xffffffff to your kernel boot options. Look for the documentation of your distro."); - warning_label.set_selectable(true); - - container.add(&warning_label); - - Self { container } - } - - pub fn show(&self) { - self.container.set_visible(true); - } - - pub fn hide(&self) { - self.container.set_visible(false); - } -} diff --git a/gui/src/app/root_stack/software_page.rs b/gui/src/app/root_stack/software_page.rs deleted file mode 100644 index 48e8e7eb..00000000 --- a/gui/src/app/root_stack/software_page.rs +++ /dev/null @@ -1,54 +0,0 @@ -use gtk::prelude::*; -use gtk::*; - -#[derive(Debug, Clone)] -pub struct SoftwarePage { - pub container: Grid, - lact_version_label: Label, -} - -impl SoftwarePage { - pub fn new() -> Self { - let container = Grid::new(); - - container.set_margin_start(5); - container.set_margin_end(5); - container.set_margin_bottom(5); - container.set_margin_top(5); - - container.set_column_spacing(10); - - container.attach( - &{ - let label = Label::new(None); - label.set_markup("LACT Version:"); - label.set_halign(Align::End); - label.set_hexpand(true); - label - }, - 0, - 0, - 1, - 1, - ); - let lact_version_label = Label::new(None); - - let lact_version = env!("CARGO_PKG_VERSION"); - let lact_release_type = match cfg!(debug_assertions) { - true => "debug", - false => "release", - }; - - lact_version_label.set_markup(&format!("{}-{}", lact_version, lact_release_type)); - - lact_version_label.set_hexpand(true); - lact_version_label.set_halign(Align::Start); - - container.attach(&lact_version_label, 1, 0, 1, 1); - - Self { - container, - lact_version_label, - } - } -} diff --git a/gui/src/app/root_stack/thermals_page.rs b/gui/src/app/root_stack/thermals_page.rs deleted file mode 100644 index d6a69f95..00000000 --- a/gui/src/app/root_stack/thermals_page.rs +++ /dev/null @@ -1,208 +0,0 @@ -mod fan_curve_frame; - -use daemon::gpu_controller::{FanControlInfo, GpuStats}; -use gtk::prelude::*; -use gtk::*; -use std::collections::BTreeMap; - -use fan_curve_frame::FanCurveFrame; - -pub struct ThermalsSettings { - pub automatic_fan_control_enabled: bool, - pub curve: BTreeMap, -} - -#[derive(Clone)] -pub struct ThermalsPage { - pub container: Box, - temp_label: Label, - fan_speed_label: Label, - fan_control_enabled_switch: Switch, - fan_curve_frame: FanCurveFrame, -} - -impl ThermalsPage { - pub fn new() -> Self { - let container = Box::new(Orientation::Vertical, 5); - - let grid = Grid::new(); - - grid.set_margin_start(5); - grid.set_margin_end(5); - grid.set_margin_bottom(5); - grid.set_margin_top(5); - - grid.set_column_homogeneous(true); - - grid.set_row_spacing(7); - grid.set_column_spacing(5); - - grid.attach( - &{ - let label = Label::new(Some("Temperatures:")); - label.set_halign(Align::End); - label - }, - 0, - 0, - 1, - 1, - ); - - let temp_label = Label::new(None); - temp_label.set_halign(Align::Start); - - grid.attach(&temp_label, 2, 0, 1, 1); - - grid.attach( - &{ - let label = Label::new(Some("Fan speed:")); - label.set_halign(Align::End); - label - }, - 0, - 1, - 1, - 1, - ); - - let fan_speed_label = Label::new(None); - fan_speed_label.set_halign(Align::Start); - - grid.attach(&fan_speed_label, 2, 1, 1, 1); - - grid.attach( - &{ - let label = Label::new(Some("Automatic fan control:")); - label.set_halign(Align::End); - label - }, - 0, - 2, - 1, - 1, - ); - - let fan_control_enabled_switch = Switch::new(); - - fan_control_enabled_switch.set_active(true); - fan_control_enabled_switch.set_halign(Align::Start); - - grid.attach(&fan_control_enabled_switch, 2, 2, 1, 1); - - container.pack_start(&grid, false, false, 5); - - let fan_curve_frame = FanCurveFrame::new(); - - container.pack_start(&fan_curve_frame.container, true, true, 5); - - // Show/hide fan curve when the switch is toggled - { - let fan_curve_frame = fan_curve_frame.clone(); - fan_control_enabled_switch.connect_changed_active(move |switch| { - log::trace!("Fan control switch toggled"); - if switch.state() { - { - glib::idle_add(|| { - let diag = MessageDialog::new(None::<&Window>, DialogFlags::empty(), MessageType::Warning, ButtonsType::Ok, - "Warning! Due to a driver bug, a reboot may be required for fan control to properly switch back to automatic."); - diag.run(); - diag.hide(); - glib::Continue(false) - }); - } - - fan_curve_frame.hide(); - } else { - fan_curve_frame.show(); - } - }); - } - - Self { - container, - temp_label, - fan_speed_label, - fan_control_enabled_switch, - fan_curve_frame, - } - } - - pub fn set_thermals_info(&self, stats: &GpuStats) { - self.temp_label.set_markup(&format!("{}", { - let mut temperatures = Vec::new(); - - for (label, temp) in stats.temperatures.iter() { - temperatures.push(format!("{}: {}°C", label, temp.current)); - } - - temperatures.sort(); - - if !temperatures.is_empty() { - temperatures.join("\n") - } else { - String::from("No sensors found") - } - })); - - match stats.fan_speed { - Some(fan_speed) => self.fan_speed_label.set_markup(&format!( - "{} RPM ({}%)", - fan_speed, - (fan_speed as f64 / stats.max_fan_speed.unwrap() as f64 * 100.0).round() - )), - None => self.fan_speed_label.set_text("No fan detected"), - } - } - - pub fn set_ventilation_info(&self, fan_control_info: FanControlInfo) { - log::info!("Setting fan control info {:?}", fan_control_info); - - self.fan_control_enabled_switch.set_visible(true); - - self.fan_control_enabled_switch - .set_active(!fan_control_info.enabled); - - if !fan_control_info.enabled { - self.fan_curve_frame.hide(); - } else { - self.fan_curve_frame.show(); - } - - self.fan_curve_frame.set_curve(&fan_control_info.curve); - } - - pub fn connect_settings_changed(&self, f: F) { - // Fan control switch toggled - { - let f = f.clone(); - self.fan_control_enabled_switch - .connect_changed_active(move |_| { - f(); - }); - } - - // Fan curve adjusted - { - let f = f.clone(); - self.fan_curve_frame.connect_adjusted(move || { - f(); - }); - } - } - - pub fn get_thermals_settings(&self) -> ThermalsSettings { - let automatic_fan_control_enabled = self.fan_control_enabled_switch.state(); - let curve = self.fan_curve_frame.get_curve(); - - ThermalsSettings { - automatic_fan_control_enabled, - curve, - } - } - - pub fn hide_fan_controls(&self) { - self.fan_control_enabled_switch.set_visible(false); - self.fan_curve_frame.hide(); - } -} diff --git a/gui/src/app/root_stack/thermals_page/fan_curve_frame.rs b/gui/src/app/root_stack/thermals_page/fan_curve_frame.rs deleted file mode 100644 index 60f0fdab..00000000 --- a/gui/src/app/root_stack/thermals_page/fan_curve_frame.rs +++ /dev/null @@ -1,217 +0,0 @@ -use std::collections::BTreeMap; - -use gtk::prelude::*; -use gtk::*; - -#[derive(Clone)] -pub struct FanCurveFrame { - pub container: Frame, - adjustment_1: Adjustment, - adjustment_2: Adjustment, - adjustment_3: Adjustment, - adjustment_4: Adjustment, - adjustment_5: Adjustment, -} - -impl FanCurveFrame { - pub fn new() -> Self { - let container = Frame::new(Some("Fan Curve")); - - container.set_margin_start(10); - container.set_margin_end(10); - container.set_margin_bottom(10); - container.set_margin_top(10); - - container.set_label_align(0.35, 0.5); - - // container.set_shadow_type(ShadowType::None); - // - let root_grid = Grid::new(); - - // PWM Percentage Labels - { - root_grid.attach( - &{ - let label = Label::new(Some("PWM %")); - label.set_angle(90.0); - label.set_vexpand(true); // This expands the entire top section of the grid, including the scales - label - }, - 0, - 0, - 1, - 5, - ); - - root_grid.attach( - &{ - let label = Label::new(Some("0")); - label.set_angle(90.0); - label - }, - 1, - 4, - 1, - 1, - ); - root_grid.attach( - &{ - let label = Label::new(Some("25")); - label.set_angle(90.0); - label - }, - 1, - 3, - 1, - 1, - ); - root_grid.attach( - &{ - let label = Label::new(Some("50")); - label.set_angle(90.0); - label - }, - 1, - 2, - 1, - 1, - ); - root_grid.attach( - &{ - let label = Label::new(Some("75")); - label.set_angle(90.0); - label - }, - 1, - 1, - 1, - 1, - ); - root_grid.attach( - &{ - let label = Label::new(Some("100")); - label.set_angle(90.0); - label - }, - 1, - 0, - 1, - 1, - ); - } - - // Temperature threshold labels - { - root_grid.attach( - &{ - let label = Label::new(Some("Temperature °C")); - label.set_hexpand(true); - label - }, - 2, - 7, - 5, - 1, - ); - - root_grid.attach(&Label::new(Some("20")), 2, 6, 1, 1); - root_grid.attach(&Label::new(Some("40")), 3, 6, 1, 1); - root_grid.attach(&Label::new(Some("60")), 4, 6, 1, 1); - root_grid.attach(&Label::new(Some("80")), 5, 6, 1, 1); - root_grid.attach(&Label::new(Some("100")), 6, 6, 1, 1); - } - - // The actual adjustments - let adjustment_1 = Adjustment::new(0.0, 0.0, 100.0, 1.0, 0.0, 0.0); // 20 °C - let adjustment_2 = Adjustment::new(0.0, 0.0, 100.0, 1.0, 0.0, 0.0); // 40 °C - let adjustment_3 = Adjustment::new(0.0, 0.0, 100.0, 1.0, 0.0, 0.0); // 60 °C - let adjustment_4 = Adjustment::new(0.0, 0.0, 100.0, 1.0, 0.0, 0.0); // 80 °C - let adjustment_5 = Adjustment::new(0.0, 0.0, 100.0, 1.0, 0.0, 0.0); // 100 °C - - // Scales for the adjustments - { - let adjustments = [ - &adjustment_1, - &adjustment_2, - &adjustment_3, - &adjustment_4, - &adjustment_5, - ]; - - for i in 0..adjustments.len() { - let adj = adjustments[i]; - - root_grid.attach( - &{ - let scale = Scale::new(Orientation::Vertical, Some(adj)); - scale.set_draw_value(false); - scale.set_inverted(true); - scale - }, - i as i32 + 2, - 0, - 1, - 5, - ); - } - } - - container.add(&root_grid); - - Self { - container, - adjustment_1, - adjustment_2, - adjustment_3, - adjustment_4, - adjustment_5, - } - } - - pub fn set_curve(&self, curve: &BTreeMap) { - self.adjustment_1.set_value(*curve.get(&20).unwrap()); - self.adjustment_2.set_value(*curve.get(&40).unwrap()); - self.adjustment_3.set_value(*curve.get(&60).unwrap()); - self.adjustment_4.set_value(*curve.get(&80).unwrap()); - self.adjustment_5.set_value(*curve.get(&100).unwrap()); - } - - pub fn get_curve(&self) -> BTreeMap { - let mut curve = BTreeMap::new(); - - curve.insert(20, self.adjustment_1.value()); - curve.insert(40, self.adjustment_2.value()); - curve.insert(60, self.adjustment_3.value()); - curve.insert(80, self.adjustment_4.value()); - curve.insert(100, self.adjustment_5.value()); - - curve - } - - pub fn show(&self) { - log::info!("Manual fan control enaged, showing fan curve"); - self.container.set_visible(true); - } - - pub fn hide(&self) { - log::info!("Manual fan control disenaged, hiding fan curve"); - self.container.set_visible(false); - } - - pub fn connect_adjusted(&self, f: F) { - let adjustments = [ - &self.adjustment_1, - &self.adjustment_2, - &self.adjustment_3, - &self.adjustment_4, - &self.adjustment_5, - ]; - - for adj in adjustments.iter() { - let f = f.clone(); - adj.connect_value_changed(move |_| { - f(); - }); - } - } -} diff --git a/gui/src/main.rs b/gui/src/main.rs deleted file mode 100644 index 359092a6..00000000 --- a/gui/src/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::thread; - -use app::App; -use daemon::{daemon_connection::DaemonConnection, Daemon}; -use gtk::prelude::*; -use gtk::*; - -mod app; - -fn main() { - env_logger::init(); - if gtk::init().is_err() { - panic!("Cannot initialize GTK"); - } - - let connection = connect_daemon(); - - ask_for_online_update(&connection); - - let app = App::new(connection); - - app.run().unwrap(); -} - -fn ask_for_online_update(connection: &DaemonConnection) { - let mut config = connection.get_config().unwrap(); - - if let None = config.allow_online_update { - log::trace!("Online access permission not configured! Showing the dialog"); - - let diag = MessageDialog::new( - None::<&Window>, - DialogFlags::empty(), - MessageType::Warning, - ButtonsType::YesNo, - "Do you wish to use the online database for GPU identification?", - ); - match diag.run() { - ResponseType::Yes => config.allow_online_update = Some(true), - ResponseType::No => config.allow_online_update = Some(false), - _ => unreachable!(), - } - diag.hide(); - - connection.set_config(config).unwrap(); - } -} - -fn connect_daemon() -> DaemonConnection { - match DaemonConnection::new() { - Ok(connection) => { - println!("Connection to daemon established"); - connection - } - Err(e) => { - println!("Error {:?} connecting to daemon", e); - println!("Starting unprivileged daemon instance"); - - thread::spawn(move || { - let daemon = Daemon::new(true); - daemon.listen(); - }); - - let dialog = MessageDialog::new( - None::<>k::Window>, - DialogFlags::empty(), - gtk::MessageType::Warning, - gtk::ButtonsType::Ok, - "Unable to connect to daemon. Running in unprivileged mode.", - ); - - dialog.run(); - dialog.close(); - - DaemonConnection::new().unwrap() - } - } -} diff --git a/lact-cli/Cargo.toml b/lact-cli/Cargo.toml new file mode 100644 index 00000000..638115ca --- /dev/null +++ b/lact-cli/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "lact-cli" +version = "0.2.0" +edition = "2021" + +[dependencies] +lact-client = { path = "../lact-client" } +anyhow = "1.0.69" +clap = { version = "4.1.6", features = ["derive"] } diff --git a/lact-cli/src/args.rs b/lact-cli/src/args.rs new file mode 100644 index 00000000..6ac7e074 --- /dev/null +++ b/lact-cli/src/args.rs @@ -0,0 +1,35 @@ +use clap::{Parser, Subcommand}; +use lact_client::DaemonClient; + +#[derive(Parser)] +#[command(author, version, about)] +pub struct CliArgs { + pub gpu_id: Option, + #[command(subcommand)] + pub subcommand: CliCommand, +} + +#[derive(Subcommand)] +pub enum CliCommand { + /// List GPUs + ListGpus, + /// Show GPU info + Info, +} + +impl CliArgs { + pub fn gpu_ids(&self, client: &DaemonClient) -> Vec { + match self.gpu_id { + Some(ref id) => vec![id.clone()], + None => { + let buffer = client.list_devices().expect("Could not list GPUs"); + buffer + .inner() + .expect("Could not deserialize GPUs response") + .into_iter() + .map(|entry| entry.id.to_owned()) + .collect() + } + } + } +} diff --git a/lact-cli/src/lib.rs b/lact-cli/src/lib.rs new file mode 100644 index 00000000..3ff01101 --- /dev/null +++ b/lact-cli/src/lib.rs @@ -0,0 +1,49 @@ +pub mod args; + +use anyhow::{Context, Result}; +use args::{CliArgs, CliCommand}; +use lact_client::DaemonClient; + +pub fn run(args: CliArgs) -> Result<()> { + let client = DaemonClient::connect()?; + + let f = match args.subcommand { + CliCommand::ListGpus => list_gpus, + CliCommand::Info => info, + }; + f(&args, &client) +} + +fn list_gpus(_: &CliArgs, client: &DaemonClient) -> Result<()> { + let buffer = client.list_devices()?; + for entry in buffer.inner()? { + let id = entry.id; + if let Some(name) = entry.name { + println!("{id} ({name})"); + } else { + println!("{id}"); + } + } + Ok(()) +} + +fn info(args: &CliArgs, client: &DaemonClient) -> Result<()> { + for id in args.gpu_ids(client) { + let info_buffer = client.get_device_info(&id)?; + let info = info_buffer.inner()?; + let pci_info = info.pci_info.context("GPU reports no pci info")?; + + if let Some(ref vendor) = pci_info.device_pci_info.vendor { + println!("GPU Vendor: {vendor}"); + } + if let Some(ref model) = pci_info.device_pci_info.model { + println!("GPU Model: {model}"); + } + println!("Driver in use: {}", info.driver); + if let Some(ref vbios_version) = info.vbios_version { + println!("VBIOS version: {vbios_version}"); + } + println!("Link: {:?}", info.link_info); + } + Ok(()) +} diff --git a/lact-client/Cargo.toml b/lact-client/Cargo.toml new file mode 100644 index 00000000..804b08e6 --- /dev/null +++ b/lact-client/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "lact-client" +version = "0.2.0" +edition = "2021" + +[dependencies] +lact-schema = { path = "../lact-schema" } +anyhow = "1.0.69" +nix = { version = "0.26.2", default-features = false } +serde = "1.0.152" +tracing = "0.1.37" +serde_json = "1.0.93" diff --git a/lact-client/src/lib.rs b/lact-client/src/lib.rs new file mode 100644 index 00000000..55a32d05 --- /dev/null +++ b/lact-client/src/lib.rs @@ -0,0 +1,143 @@ +#[macro_use] +mod macros; + +pub use lact_schema as schema; + +use anyhow::{anyhow, Context}; +use nix::unistd::getuid; +use schema::{ + request::SetClocksCommand, ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanCurveMap, + PerformanceLevel, Request, Response, SystemInfo, +}; +use serde::Deserialize; +use std::{ + io::{BufRead, BufReader, Write}, + marker::PhantomData, + ops::DerefMut, + os::unix::net::UnixStream, + path::PathBuf, + sync::{Arc, Mutex}, +}; +use tracing::info; + +#[derive(Clone)] +pub struct DaemonClient { + stream: Arc, UnixStream)>>, + pub embedded: bool, +} + +impl DaemonClient { + pub fn connect() -> anyhow::Result { + let path = + get_socket_path().context("Could not connect to daemon: socket file not found")?; + info!("connecting to service at {path:?}"); + let stream = UnixStream::connect(path).context("Could not connect to daemon")?; + Self::from_stream(stream, false) + } + + pub fn from_stream(stream: UnixStream, embedded: bool) -> anyhow::Result { + let reader = BufReader::new(stream.try_clone()?); + Ok(Self { + stream: Arc::new(Mutex::new((reader, stream))), + embedded, + }) + } + + fn make_request<'a, T: Deserialize<'a>>( + &self, + request: Request, + ) -> anyhow::Result> { + let mut stream_guard = self.stream.lock().map_err(|err| anyhow!("{err}"))?; + let (reader, writer) = stream_guard.deref_mut(); + + if !reader.buffer().is_empty() { + return Err(anyhow!("Another request was not processed properly")); + } + + let request_payload = serde_json::to_string(&request)?; + writer.write_all(request_payload.as_bytes())?; + writer.write_all(b"\n")?; + + let mut response_payload = String::new(); + reader.read_line(&mut response_payload)?; + + Ok(ResponseBuffer { + buf: response_payload, + _phantom: PhantomData, + }) + } + + pub fn list_devices<'a>(&self) -> anyhow::Result>>> { + self.make_request(Request::ListDevices) + } + + pub fn set_fan_control( + &self, + id: &str, + enabled: bool, + curve: Option, + ) -> anyhow::Result<()> { + self.make_request::<()>(Request::SetFanControl { id, enabled, curve })? + .inner()?; + Ok(()) + } + + pub fn set_power_cap(&self, id: &str, cap: Option) -> anyhow::Result<()> { + self.make_request(Request::SetPowerCap { id, cap })?.inner() + } + + request_plain!(get_system_info, SystemInfo, SystemInfo); + request_with_id!(get_device_info, DeviceInfo, DeviceInfo); + request_with_id!(get_device_stats, DeviceStats, DeviceStats); + request_with_id!(get_device_clocks_info, DeviceClocksInfo, ClocksInfo); + + pub fn set_performance_level( + &self, + id: &str, + performance_level: PerformanceLevel, + ) -> anyhow::Result<()> { + self.make_request(Request::SetPerformanceLevel { + id, + performance_level, + })? + .inner() + } + + pub fn set_clocks_value(&self, id: &str, command: SetClocksCommand) -> anyhow::Result<()> { + self.make_request(Request::SetClocksValue { id, command })? + .inner() + } +} + +fn get_socket_path() -> Option { + let root_path = PathBuf::from("/var/run/lactd.sock"); + + if root_path.exists() { + return Some(root_path); + } + + let uid = getuid(); + let user_path = PathBuf::from(format!("/var/run/user/{}/lactd.sock", uid)); + + if user_path.exists() { + Some(user_path) + } else { + None + } +} + +pub struct ResponseBuffer { + buf: String, + _phantom: PhantomData, +} + +impl<'a, T: Deserialize<'a>> ResponseBuffer { + pub fn inner(&'a self) -> anyhow::Result { + let response: Response = serde_json::from_str(&self.buf) + .context("Could not deserialize response from daemon")?; + match response { + Response::Ok(data) => Ok(data), + Response::Error(err) => Err(anyhow!("Got error from daemon: {err}")), + } + } +} diff --git a/lact-client/src/macros.rs b/lact-client/src/macros.rs new file mode 100644 index 00000000..1895899a --- /dev/null +++ b/lact-client/src/macros.rs @@ -0,0 +1,15 @@ +macro_rules! request_with_id { + ($name:ident, $variant:ident, $response:ty) => { + pub fn $name(&self, id: &str) -> anyhow::Result> { + self.make_request(Request::$variant { id }) + } + }; +} + +macro_rules! request_plain { + ($name:ident, $variant:ident, $response:ty) => { + pub fn $name(&self) -> anyhow::Result> { + self.make_request(Request::$variant) + } + }; +} diff --git a/lact-daemon/Cargo.toml b/lact-daemon/Cargo.toml new file mode 100644 index 00000000..b6ca2024 --- /dev/null +++ b/lact-daemon/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "lact-daemon" +version = "0.2.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +amdgpu-sysfs = { version = "0.9.3", features = ["serde"] } +anyhow = "1.0" +bincode = "1.3" +nix = "0.26" +pciid-parser = { version = "0.6", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" +tokio = { version = "1.25.0", features = [ + "rt", + "macros", + "net", + "io-util", + "time", + "signal", + "sync", +] } +tracing = "0.1" +tracing-subscriber = "0.3" +vulkano = { git = "https://github.com/vulkano-rs/vulkano" } +lact-schema = { path = "../lact-schema" } +futures = { version = "0.3.26", default-features = false, features = [ + "std", + "alloc", +] } +serde_with = { version = "2.2.0", default-features = false, features = [ + "macros", +] } diff --git a/lact-daemon/src/config.rs b/lact-daemon/src/config.rs new file mode 100644 index 00000000..1e121622 --- /dev/null +++ b/lact-daemon/src/config.rs @@ -0,0 +1,126 @@ +use crate::server::gpu_controller::fan_control::FanCurve; +use anyhow::Context; +use lact_schema::PerformanceLevel; +use nix::unistd::getuid; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +use std::{collections::HashMap, env, fs, path::PathBuf}; +use tracing::debug; + +const FILE_NAME: &str = "config.yaml"; +const DEFAULT_ADMIN_GROUPS: [&str; 2] = ["wheel", "sudo"]; + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct Config { + pub daemon: Daemon, + pub gpus: HashMap, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct Daemon { + pub log_level: String, + pub admin_groups: Vec, +} + +impl Default for Daemon { + fn default() -> Self { + Self { + log_level: "info".to_owned(), + admin_groups: DEFAULT_ADMIN_GROUPS.map(str::to_owned).to_vec(), + } + } +} + +#[skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] +pub struct Gpu { + pub fan_control_enabled: bool, + pub fan_control_settings: Option, + pub power_cap: Option, + pub performance_level: Option, + pub max_core_clock: Option, + pub max_memory_clock: Option, + pub max_voltage: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct FanControlSettings { + pub temperature_key: String, + pub interval_ms: u64, + pub curve: FanCurve, +} + +impl Config { + pub fn load() -> anyhow::Result> { + let path = get_path(); + if path.exists() { + let raw_config = fs::read_to_string(path).context("Could not open config file")?; + let config = + serde_yaml::from_str(&raw_config).context("Could not deserialize config")?; + Ok(Some(config)) + } else { + let parent = path.parent().unwrap(); + fs::create_dir_all(parent)?; + Ok(None) + } + } + + pub fn save(&self) -> anyhow::Result<()> { + let path = get_path(); + debug!("saving config to {path:?}"); + let raw_config = serde_yaml::to_string(self)?; + fs::write(path, raw_config).context("Could not write config") + } + + pub fn load_or_create() -> anyhow::Result { + if let Some(config) = Config::load()? { + Ok(config) + } else { + let config = Config::default(); + config.save()?; + Ok(config) + } + } +} + +fn get_path() -> PathBuf { + let uid = getuid(); + if uid.is_root() { + PathBuf::from("/etc/lact").join(FILE_NAME) + } else { + let config_dir = PathBuf::from(env::var("XDG_CONFIG_HOME").unwrap_or_else(|_| { + let home = env::var("HOME").expect("$HOME variable is not set"); + format!("{home}/.config") + })); + config_dir.join("lact").join(FILE_NAME) + } +} + +#[cfg(test)] +mod tests { + use super::{Config, Daemon, FanControlSettings, Gpu}; + use crate::server::gpu_controller::fan_control::FanCurve; + + #[test] + fn serde_de_full() { + let config = Config { + daemon: Daemon::default(), + gpus: [( + "my-gpu-id".to_owned(), + Gpu { + fan_control_enabled: true, + fan_control_settings: Some(FanControlSettings { + curve: FanCurve::default(), + temperature_key: "edge".to_owned(), + interval_ms: 500, + }), + ..Default::default() + }, + )] + .into(), + }; + let data = serde_yaml::to_string(&config).unwrap(); + let deserialized_config: Config = serde_yaml::from_str(&data).unwrap(); + assert_eq!(config, deserialized_config); + } +} diff --git a/lact-daemon/src/fork.rs b/lact-daemon/src/fork.rs new file mode 100644 index 00000000..8ffb048f --- /dev/null +++ b/lact-daemon/src/fork.rs @@ -0,0 +1,100 @@ +use anyhow::{anyhow, Context}; +use nix::{ + sys::wait::waitpid, + unistd::{fork, ForkResult}, +}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{ + fmt::Debug, + io::{BufReader, Read, Write}, + mem::size_of, + os::unix::net::UnixStream, +}; +use tracing::trace; + +pub unsafe fn run_forked(f: F) -> anyhow::Result +where + T: Serialize + DeserializeOwned + Debug, + F: FnOnce() -> Result, +{ + let (rx, mut tx) = UnixStream::pair()?; + let mut rx = BufReader::new(rx); + + match fork()? { + ForkResult::Parent { child } => { + trace!("waiting for message from child"); + + let mut size_buf = [0u8; size_of::()]; + rx.read_exact(&mut size_buf)?; + let size = usize::from_ne_bytes(size_buf); + + let mut data_buf = vec![0u8; size]; + rx.read_exact(&mut data_buf)?; + + trace!("received {} data bytes from child", data_buf.len()); + + waitpid(child, None)?; + + let data: Result = bincode::deserialize(&data_buf) + .context("Could not deserialize response from child")?; + + data.map_err(|err| anyhow!("{err}")) + } + ForkResult::Child => { + let response = f(); + trace!("sending response to parent: {response:?}"); + + let send_result = (|| { + let data = bincode::serialize(&response)?; + tx.write_all(&data.len().to_ne_bytes())?; + tx.write_all(&data)?; + Ok::<_, anyhow::Error>(()) + })(); + + let exit_code = match send_result { + Ok(()) => 0, + Err(_) => 1, + }; + trace!("exiting child with code {exit_code}"); + std::process::exit(exit_code); + } + } +} + +#[cfg(test)] +mod tests { + use super::run_forked; + + #[test] + fn basic() { + let response = unsafe { run_forked(|| Ok(String::from("hello"))).unwrap() }; + assert_eq!(response, "hello"); + } + + #[test] + fn error() { + let response = + unsafe { run_forked::<(), _>(|| Err("something went wrong".to_owned())) }.unwrap_err(); + assert_eq!(response.to_string(), "something went wrong"); + } + + #[test] + fn vec() { + let response = unsafe { + run_forked(|| { + let data = ["hello", "world", "123"].map(str::to_owned); + Ok(data) + }) + .unwrap() + }; + assert_eq!(response, ["hello", "world", "123"]); + } + + #[test] + fn pci_db() { + let db = unsafe { + run_forked(|| pciid_parser::Database::read().map_err(|err| err.to_string())).unwrap() + }; + assert_ne!(db.classes.len(), 0); + } +} diff --git a/lact-daemon/src/lib.rs b/lact-daemon/src/lib.rs new file mode 100644 index 00000000..31709b4d --- /dev/null +++ b/lact-daemon/src/lib.rs @@ -0,0 +1,84 @@ +#![warn(clippy::pedantic)] + +mod config; +mod fork; +mod server; +mod socket; + +use anyhow::Context; +use config::Config; +use futures::future::select_all; +use server::{handle_stream, handler::Handler, Server}; +use std::os::unix::net::UnixStream as StdUnixStream; +use std::str::FromStr; +use tokio::{ + runtime, + signal::unix::{signal, SignalKind}, +}; +use tracing::{debug_span, info, Instrument, Level}; + +const SHUTDOWN_SIGNALS: [SignalKind; 4] = [ + SignalKind::terminate(), + SignalKind::interrupt(), + SignalKind::quit(), + SignalKind::hangup(), +]; + +/// Run the daemon, binding to the default socket. +/// +/// # Errors +/// Returns an error when the daemon cannot initialize. +pub fn run() -> anyhow::Result<()> { + let rt = runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Could not initialize tokio runtime"); + rt.block_on(async { + let config = Config::load_or_create()?; + + let max_level = Level::from_str(&config.daemon.log_level).context("Invalid log level")?; + tracing_subscriber::fmt().with_max_level(max_level).init(); + + let server = Server::new(config).await?; + let handler = server.handler.clone(); + + tokio::spawn(listen_shutdown(handler)); + server.run().await; + Ok(()) + }) +} + +/// Run the daemon with a given `UnixStream`. +/// This will NOT bind to a socket by itself, and the daemon will only be accessible via the given stream. +/// +/// # Errors +/// Returns an error when the daemon cannot initialize. +pub fn run_embedded(stream: StdUnixStream) -> anyhow::Result<()> { + let rt = runtime::Builder::new_current_thread() + .enable_all() + .build() + .expect("Could not initialize tokio runtime"); + rt.block_on(async { + let config = Config::default(); + let handler = Handler::new(config).await?; + let stream = stream.try_into()?; + + handle_stream(stream, handler).await + }) +} + +async fn listen_shutdown(handler: Handler) { + let mut signals = SHUTDOWN_SIGNALS + .map(|signal_kind| signal(signal_kind).expect("Could not listen to shutdown signal")); + let signal_futures = signals.iter_mut().map(|signal| Box::pin(signal.recv())); + select_all(signal_futures).await; + + info!("cleaning up and shutting down..."); + async { + handler.cleanup().await; + socket::cleanup(); + } + .instrument(debug_span!("shutdown_cleanup")) + .await; + std::process::exit(0); +} diff --git a/lact-daemon/src/server/gpu_controller/fan_control.rs b/lact-daemon/src/server/gpu_controller/fan_control.rs new file mode 100644 index 00000000..65852721 --- /dev/null +++ b/lact-daemon/src/server/gpu_controller/fan_control.rs @@ -0,0 +1,162 @@ +use amdgpu_sysfs::hw_mon::Temperature; +use anyhow::anyhow; +use lact_schema::{default_fan_curve, FanCurveMap}; +use serde::{Deserialize, Serialize}; +use tracing::warn; + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct FanCurve(pub FanCurveMap); + +#[allow( + clippy::cast_possible_truncation, + clippy::cast_precision_loss, + clippy::cast_sign_loss +)] +impl FanCurve { + pub fn pwm_at_temp(&self, temp: Temperature) -> u8 { + let current = temp.current.expect("No current temp"); + + // This scenario is most likely unreachable as the kernel shuts down the GPU when it reaches critical temperature + if temp.crit.filter(|crit| current > *crit).is_some() + || temp.crit_hyst.filter(|hyst| current < *hyst).is_some() + { + warn!("GPU temperature is beyond critical values! {current}°C"); + return u8::MAX; + } + + let current = current as i32; + let maybe_lower = self.0.range(..current).next_back(); + let maybe_higher = self.0.range(current..).next(); + + let percentage = match (maybe_lower, maybe_higher) { + (Some((lower_temp, lower_speed)), Some((higher_temp, higher_speed))) => { + let speed_ratio = (current - lower_temp) as f32 / (higher_temp - lower_temp) as f32; + lower_speed + (higher_speed - lower_speed) * speed_ratio + } + (Some((_, lower_speed)), None) => *lower_speed, + (None, Some((_, higher_speed))) => *higher_speed, + (None, None) => panic!("Could not find fan speed on the curve! This is a bug."), + }; + + (f32::from(u8::MAX) * percentage) as u8 + } +} + +impl FanCurve { + pub fn validate(&self) -> anyhow::Result<()> { + for percentage in self.0.values() { + if !(0.0..=1.0).contains(percentage) { + return Err(anyhow!("Fan speed percentage must be between 0 and 1")); + } + } + Ok(()) + } +} + +impl Default for FanCurve { + fn default() -> Self { + Self(default_fan_curve()) + } +} + +#[cfg(test)] +mod tests { + use super::FanCurve; + use amdgpu_sysfs::hw_mon::Temperature; + + fn simple_pwm(temp: f32) -> u8 { + let curve = FanCurve([(0, 0.0), (100, 1.0)].into()); + let temp = Temperature { + current: Some(temp), + crit: Some(150.0), + crit_hyst: Some(-100.0), + }; + curve.pwm_at_temp(temp) + } + + #[test] + fn simple_curve_middle() { + let pwm = simple_pwm(45.0); + assert_eq!(pwm, 114); + } + + #[test] + fn simple_curve_start() { + let pwm = simple_pwm(0.0); + assert_eq!(pwm, 0); + } + + #[test] + fn simple_curve_end() { + let pwm = simple_pwm(100.0); + assert_eq!(pwm, 255); + } + + #[test] + fn simple_curve_before() { + let pwm = simple_pwm(-5.0); + assert_eq!(pwm, 0); + } + + #[test] + fn simple_curve_after() { + let pwm = simple_pwm(105.0); + assert_eq!(pwm, 255); + } + + #[test] + fn curve_crit() { + let curve = FanCurve([(20, 0.0), (80, 100.0)].into()); + let temp = Temperature { + current: Some(100.0), + crit: Some(90.0), + crit_hyst: Some(0.0), + }; + let pwm = curve.pwm_at_temp(temp); + assert_eq!(pwm, 255); + } + + #[test] + fn uneven_curve() { + let curve = FanCurve([(30, 0.0), (40, 0.1), (55, 0.9), (61, 1.0)].into()); + let pwm_at_temp = |current: f32| { + let temp = Temperature { + current: Some(current), + crit: Some(90.0), + crit_hyst: Some(0.0), + }; + curve.pwm_at_temp(temp) + }; + + assert_eq!(pwm_at_temp(30.0), 0); + assert_eq!(pwm_at_temp(35.0), 12); + assert_eq!(pwm_at_temp(40.0), 25); + assert_eq!(pwm_at_temp(47.0), 120); + assert_eq!(pwm_at_temp(52.0), 188); + assert_eq!(pwm_at_temp(53.0), 202); + assert_eq!(pwm_at_temp(54.0), 215); + } + + #[test] + fn default_curve() { + let curve = FanCurve::default(); + let pwm_at_temp = |current: f32| { + let temp = Temperature { + current: Some(current), + crit: Some(90.0), + crit_hyst: Some(0.0), + }; + curve.pwm_at_temp(temp) + }; + assert_eq!(pwm_at_temp(20.0), 0); + assert_eq!(pwm_at_temp(30.0), 0); + assert_eq!(pwm_at_temp(33.0), 15); + assert_eq!(pwm_at_temp(60.0), 127); + assert_eq!(pwm_at_temp(65.0), 159); + assert_eq!(pwm_at_temp(70.0), 191); + assert_eq!(pwm_at_temp(79.0), 248); + assert_eq!(pwm_at_temp(85.0), 255); + assert_eq!(pwm_at_temp(100.0), 255); + assert_eq!(pwm_at_temp(-5.0), 255); + } +} diff --git a/lact-daemon/src/server/gpu_controller/mod.rs b/lact-daemon/src/server/gpu_controller/mod.rs new file mode 100644 index 00000000..1844e348 --- /dev/null +++ b/lact-daemon/src/server/gpu_controller/mod.rs @@ -0,0 +1,367 @@ +pub mod fan_control; + +use self::fan_control::FanCurve; +use super::vulkan::get_vulkan_info; +use crate::{config, fork::run_forked}; +use amdgpu_sysfs::{ + error::Error, + gpu_handle::GpuHandle, + hw_mon::{FanControlMethod, HwMon}, + sysfs::SysFS, +}; +use anyhow::{anyhow, Context}; +use lact_schema::{ + ClocksInfo, ClocksTable, ClockspeedStats, DeviceInfo, DeviceStats, FanStats, GpuPciInfo, + LinkInfo, PciInfo, PerformanceLevel, PowerStats, VoltageStats, VramStats, +}; +use pciid_parser::Database; +use std::{ + borrow::Cow, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, + time::Duration, +}; +use tokio::{select, sync::Notify, task::JoinHandle, time::sleep}; +use tracing::{debug, error, trace, warn}; + +type FanControlHandle = (Arc, JoinHandle<()>); + +pub struct GpuController { + pub handle: GpuHandle, + pub pci_info: Option, + pub fan_control_handle: Mutex>, +} + +impl GpuController { + pub fn new_from_path(sysfs_path: PathBuf) -> anyhow::Result { + let handle = GpuHandle::new_from_path(sysfs_path) + .map_err(|error| anyhow!("failed to initialize gpu handle: {error}"))?; + + let mut device_pci_info = None; + let mut subsystem_pci_info = None; + + if let Some((vendor_id, model_id)) = handle.get_pci_id() { + device_pci_info = Some(PciInfo { + vendor_id: vendor_id.to_owned(), + vendor: None, + model_id: model_id.to_owned(), + model: None, + }); + + if let Some((subsys_vendor_id, subsys_model_id)) = handle.get_pci_subsys_id() { + let (new_device_info, new_subsystem_info) = unsafe { + run_forked(|| { + let pci_db = Database::read().map_err(|err| err.to_string())?; + let pci_device_info = pci_db.get_device_info( + vendor_id, + model_id, + subsys_vendor_id, + subsys_model_id, + ); + + let device_pci_info = PciInfo { + vendor_id: vendor_id.to_owned(), + vendor: pci_device_info.vendor_name.map(str::to_owned), + model_id: model_id.to_owned(), + model: pci_device_info.device_name.map(str::to_owned), + }; + let subsystem_pci_info = PciInfo { + vendor_id: subsys_vendor_id.to_owned(), + vendor: pci_device_info.subvendor_name.map(str::to_owned), + model_id: subsys_model_id.to_owned(), + model: pci_device_info.subdevice_name.map(str::to_owned), + }; + Ok((device_pci_info, subsystem_pci_info)) + })? + }; + device_pci_info = Some(new_device_info); + subsystem_pci_info = Some(new_subsystem_info); + } + } + + let pci_info = device_pci_info.and_then(|device_pci_info| { + Some(GpuPciInfo { + device_pci_info, + subsystem_pci_info: subsystem_pci_info?, + }) + }); + + Ok(Self { + handle, + pci_info, + fan_control_handle: Mutex::new(None), + }) + } + + pub fn get_id(&self) -> anyhow::Result { + let handle = &self.handle; + let pci_id = handle.get_pci_id().context("Device has no vendor id")?; + let pci_subsys_id = handle + .get_pci_subsys_id() + .context("Device has no subsys id")?; + let pci_slot_name = handle + .get_pci_slot_name() + .context("Device has no pci slot")?; + + Ok(format!( + "{}:{}-{}:{}-{}", + pci_id.0, pci_id.1, pci_subsys_id.0, pci_subsys_id.1, pci_slot_name + )) + } + + pub fn get_path(&self) -> &Path { + self.handle.get_path() + } + + fn first_hw_mon(&self) -> anyhow::Result<&HwMon> { + self.handle + .hw_monitors + .first() + .context("GPU has no hardware monitor") + } + + pub fn get_info(&self) -> DeviceInfo { + let vulkan_info = self.pci_info.as_ref().and_then(|pci_info| { + match get_vulkan_info( + &pci_info.device_pci_info.vendor_id, + &pci_info.device_pci_info.model_id, + ) { + Ok(info) => Some(info), + Err(err) => { + warn!("could not load vulkan info: {err}"); + None + } + } + }); + let pci_info = self.pci_info.as_ref().map(Cow::Borrowed); + let driver = self.handle.get_driver(); + let vbios_version = self.handle.get_vbios_version().ok(); + let link_info = self.get_link_info(); + + DeviceInfo { + pci_info, + vulkan_info, + driver, + vbios_version, + link_info, + } + } + + fn get_link_info(&self) -> LinkInfo { + LinkInfo { + current_width: self.handle.get_current_link_width().ok(), + current_speed: self.handle.get_current_link_speed().ok(), + max_width: self.handle.get_max_link_width().ok(), + max_speed: self.handle.get_max_link_speed().ok(), + } + } + + pub fn get_stats(&self, gpu_config: Option<&config::Gpu>) -> anyhow::Result { + let fan_control_enabled = self + .fan_control_handle + .lock() + .map_err(|err| anyhow!("Could not lock fan control mutex: {err}"))? + .is_some(); + + Ok(DeviceStats { + fan: FanStats { + control_enabled: fan_control_enabled, + curve: gpu_config + .and_then(|config| config.fan_control_settings.as_ref()) + .map(|settings| settings.curve.0.clone()), + speed_current: self.hw_mon_and_then(HwMon::get_fan_current), + speed_max: self.hw_mon_and_then(HwMon::get_fan_max), + speed_min: self.hw_mon_and_then(HwMon::get_fan_min), + }, + clockspeed: ClockspeedStats { + gpu_clockspeed: self.hw_mon_and_then(HwMon::get_gpu_clockspeed), + vram_clockspeed: self.hw_mon_and_then(HwMon::get_vram_clockspeed), + }, + voltage: VoltageStats { + gpu: self.hw_mon_and_then(HwMon::get_gpu_voltage), + northbridge: self.hw_mon_and_then(HwMon::get_northbridge_voltage), + }, + vram: VramStats { + total: self.handle.get_total_vram().ok(), + used: self.handle.get_used_vram().ok(), + }, + power: PowerStats { + average: self.hw_mon_and_then(HwMon::get_power_average), + cap_current: self.hw_mon_and_then(HwMon::get_power_cap), + cap_max: self.hw_mon_and_then(HwMon::get_power_cap_max), + cap_min: self.hw_mon_and_then(HwMon::get_power_cap_min), + cap_default: self.hw_mon_and_then(HwMon::get_power_cap_default), + }, + temps: self.hw_mon_map(HwMon::get_temps).unwrap_or_default(), + busy_percent: self.handle.get_busy_percent().ok(), + performance_level: self.handle.get_power_force_performance_level().ok(), + core_clock_levels: self.handle.get_core_clock_levels().ok(), + memory_clock_levels: self.handle.get_memory_clock_levels().ok(), + pcie_clock_levels: self.handle.get_pcie_clock_levels().ok(), + }) + } + + pub fn get_clocks_info(&self) -> anyhow::Result { + let clocks_table = self + .handle + .get_clocks_table() + .context("Clocks table not available")?; + Ok(clocks_table.into()) + } + + fn hw_mon_and_then(&self, f: fn(&HwMon) -> Result) -> Option { + self.handle.hw_monitors.first().and_then(|mon| f(mon).ok()) + } + + fn hw_mon_map(&self, f: fn(&HwMon) -> U) -> Option { + self.handle.hw_monitors.first().map(f) + } + + async fn start_fan_control( + &self, + curve: FanCurve, + temp_key: String, + interval: Duration, + ) -> anyhow::Result<()> { + // Stop existing task to re-apply new curve + self.stop_fan_control(false).await?; + + let hw_mon = self + .handle + .hw_monitors + .first() + .cloned() + .context("This GPU has no monitor")?; + hw_mon + .set_fan_control_method(FanControlMethod::Manual) + .context("Could not set fan control method")?; + + let mut notify_guard = self + .fan_control_handle + .lock() + .map_err(|err| anyhow!("Lock error: {err}"))?; + + let notify = Arc::new(Notify::new()); + let task_notify = notify.clone(); + + let handle = tokio::spawn(async move { + loop { + select! { + _ = sleep(interval) => (), + _ = task_notify.notified() => break, + } + + let mut temps = hw_mon.get_temps(); + let temp = temps + .remove(&temp_key) + .expect("Could not get temperature by given key"); + let target_pwm = curve.pwm_at_temp(temp); + trace!("fan control tick: setting pwm to {target_pwm}"); + + if let Err(err) = hw_mon.set_fan_pwm(target_pwm) { + error!("could not set fan speed: {err}, disabling fan control"); + break; + } + } + debug!("exited fan control task"); + }); + + *notify_guard = Some((notify, handle)); + + debug!( + "started fan control with interval {}ms", + interval.as_millis() + ); + + Ok(()) + } + + async fn stop_fan_control(&self, reset_mode: bool) -> anyhow::Result<()> { + let maybe_notify = self + .fan_control_handle + .lock() + .map_err(|err| anyhow!("Lock error: {err}"))? + .take(); + if let Some((notify, handle)) = maybe_notify { + notify.notify_one(); + handle.await?; + + if reset_mode { + let hw_mon = self + .handle + .hw_monitors + .first() + .cloned() + .context("This GPU has no monitor")?; + hw_mon + .set_fan_control_method(FanControlMethod::Auto) + .context("Could not set fan control back to automatic")?; + } + } + + Ok(()) + } + + pub async fn apply_config(&self, config: &config::Gpu) -> anyhow::Result<()> { + if config.fan_control_enabled { + if let Some(ref settings) = config.fan_control_settings { + if settings.curve.0.is_empty() { + return Err(anyhow!("Cannot use empty fan curve")); + } + + let interval = Duration::from_millis(settings.interval_ms); + self.start_fan_control( + settings.curve.clone(), + settings.temperature_key.clone(), + interval, + ) + .await?; + } else { + return Err(anyhow!( + "Trying to enable fan control with no settings provided" + )); + } + } else { + self.stop_fan_control(true).await?; + } + + if let Some(cap) = config.power_cap { + let hw_mon = self.first_hw_mon()?; + hw_mon.set_power_cap(cap)?; + } else if let Ok(hw_mon) = self.first_hw_mon() { + if let Ok(default_cap) = hw_mon.get_power_cap_default() { + hw_mon.set_power_cap(default_cap)?; + } + } + + if let Some(level) = config.performance_level { + self.handle.set_power_force_performance_level(level)?; + } else if self.handle.get_power_force_performance_level().is_ok() { + self.handle + .set_power_force_performance_level(PerformanceLevel::Auto)?; + } + + if config.max_core_clock.is_some() + || config.max_memory_clock.is_some() + || config.max_voltage.is_some() + { + let mut table = self.handle.get_clocks_table()?; + + if let Some(clockspeed) = config.max_core_clock { + table.set_max_sclk(clockspeed)?; + } + if let Some(clockspeed) = config.max_memory_clock { + table.set_max_mclk(clockspeed)?; + } + if let Some(voltage) = config.max_voltage { + table.set_max_voltage(voltage)?; + } + + self.handle + .set_clocks_table(&table) + .context("Could not write clocks table")?; + } + + Ok(()) + } +} diff --git a/lact-daemon/src/server/handler.rs b/lact-daemon/src/server/handler.rs new file mode 100644 index 00000000..c5f84a91 --- /dev/null +++ b/lact-daemon/src/server/handler.rs @@ -0,0 +1,239 @@ +use super::gpu_controller::{fan_control::FanCurve, GpuController}; +use crate::config::{self, Config, FanControlSettings}; +use anyhow::{anyhow, Context}; +use lact_schema::{ + request::SetClocksCommand, ClocksInfo, DeviceInfo, DeviceListEntry, DeviceStats, FanCurveMap, + PerformanceLevel, +}; +use std::{ + collections::HashMap, + env, + path::PathBuf, + sync::{Arc, RwLock}, +}; +use tracing::{debug, error, info, trace, warn}; + +#[derive(Clone)] +pub struct Handler { + pub config: Arc>, + pub gpu_controllers: Arc>, +} + +impl<'a> Handler { + pub async fn new(config: Config) -> anyhow::Result { + let mut controllers = HashMap::new(); + + let base_path = match env::var("_LACT_DRM_SYSFS_PATH") { + Ok(custom_path) => PathBuf::from(custom_path), + Err(_) => PathBuf::from("/sys/class/drm"), + }; + + for entry in base_path + .read_dir() + .map_err(|error| anyhow!("Failed to read sysfs: {error}"))? + { + let entry = entry?; + + let name = entry + .file_name() + .into_string() + .map_err(|_| anyhow!("non-utf path"))?; + if name.starts_with("card") && !name.contains('-') { + trace!("trying gpu controller at {:?}", entry.path()); + let device_path = entry.path().join("device"); + match GpuController::new_from_path(device_path) { + Ok(controller) => match controller.get_id() { + Ok(id) => { + let path = controller.get_path(); + debug!("initialized GPU controller {id} for path {path:?}",); + controllers.insert(id, controller); + } + Err(err) => warn!("could not initialize controller: {err:#}"), + }, + Err(error) => { + warn!( + "failed to initialize controller at {:?}, {error}", + entry.path() + ); + } + } + } + } + + for (id, gpu_config) in &config.gpus { + if let Some(controller) = controllers.get(id) { + controller.apply_config(gpu_config).await?; + } else { + info!("could not find GPU with id {id} defined in configuration"); + } + } + + Ok(Self { + gpu_controllers: Arc::new(controllers), + config: Arc::new(RwLock::new(config)), + }) + } + + async fn edit_gpu_config( + &self, + id: String, + f: F, + ) -> anyhow::Result<()> { + let current_config = self + .config + .read() + .map_err(|err| anyhow!("{err}"))? + .gpus + .get(&id) + .cloned() + .unwrap_or_default(); + + let mut new_config = current_config.clone(); + f(&mut new_config); + + let controller = self.controller_by_id(&id)?; + + match controller.apply_config(&new_config).await { + Ok(()) => { + let mut config_guard = self.config.write().unwrap(); + config_guard.gpus.insert(id, new_config); + config_guard.save()?; + Ok(()) + } + Err(apply_err) => { + error!("Could not apply settings: {apply_err:#}"); + match controller.apply_config(¤t_config).await { + Ok(()) => Err(apply_err.context("Could not apply settings")), + Err(err) => Err(anyhow!("Could not apply settings, and could not reset to default settings: {err:#}")), + } + } + } + } + + fn controller_by_id(&self, id: &str) -> anyhow::Result<&GpuController> { + Ok(self + .gpu_controllers + .get(id) + .as_ref() + .context("No controller with such id")?) + } + + pub fn list_devices(&'a self) -> Vec> { + self.gpu_controllers + .iter() + .map(|(id, controller)| { + let name = controller + .pci_info + .as_ref() + .and_then(|pci_info| pci_info.device_pci_info.model.as_deref()); + DeviceListEntry { id, name } + }) + .collect() + } + + pub fn get_device_info(&'a self, id: &str) -> anyhow::Result> { + Ok(self.controller_by_id(id)?.get_info()) + } + + pub fn get_gpu_stats(&'a self, id: &str) -> anyhow::Result { + let config = self + .config + .read() + .map_err(|err| anyhow!("Could not read config: {err:?}"))?; + let gpu_config = config.gpus.get(id); + self.controller_by_id(id)?.get_stats(gpu_config) + } + + pub fn get_clocks_info(&'a self, id: &str) -> anyhow::Result { + self.controller_by_id(id)?.get_clocks_info() + } + + pub async fn set_fan_control( + &'a self, + id: &str, + enabled: bool, + curve: Option, + ) -> anyhow::Result<()> { + let settings = match curve { + Some(raw_curve) => { + let curve = FanCurve(raw_curve); + curve.validate()?; + + let mut config_guard = self.config.write().map_err(|err| anyhow!("{err}"))?; + let gpu_config = config_guard.gpus.entry(id.to_owned()).or_default(); + + if let Some(mut existing_settings) = gpu_config.fan_control_settings.clone() { + existing_settings.curve = curve; + Some(existing_settings) + } else { + Some(FanControlSettings { + curve, + temperature_key: "edge".to_owned(), + interval_ms: 500, + }) + } + } + None => None, + }; + + self.edit_gpu_config(id.to_owned(), |config| { + config.fan_control_enabled = enabled; + config.fan_control_settings = settings; + }) + .await + } + + pub async fn set_power_cap(&'a self, id: &str, maybe_cap: Option) -> anyhow::Result<()> { + self.edit_gpu_config(id.to_owned(), |gpu_config| { + gpu_config.power_cap = maybe_cap; + }) + .await + } + + pub async fn set_performance_level( + &self, + id: &str, + level: PerformanceLevel, + ) -> anyhow::Result<()> { + self.edit_gpu_config(id.to_owned(), |gpu_config| { + gpu_config.performance_level = Some(level); + }) + .await + } + + pub async fn set_clocks_value( + &self, + id: &str, + command: SetClocksCommand, + ) -> anyhow::Result<()> { + if let SetClocksCommand::Reset = command { + self.controller_by_id(id)?.handle.reset_clocks_table()?; + } + + self.edit_gpu_config(id.to_owned(), |gpu_config| match command { + SetClocksCommand::MaxCoreClock(clock) => gpu_config.max_core_clock = Some(clock), + SetClocksCommand::MaxMemoryClock(clock) => gpu_config.max_memory_clock = Some(clock), + SetClocksCommand::MaxVoltage(voltage) => gpu_config.max_voltage = Some(voltage), + SetClocksCommand::Reset => { + gpu_config.max_core_clock = None; + gpu_config.max_memory_clock = None; + gpu_config.max_voltage = None; + } + }) + .await + } + + pub async fn cleanup(self) { + for (id, controller) in self.gpu_controllers.iter() { + if controller.handle.get_clocks_table().is_ok() { + if let Err(err) = controller.handle.reset_clocks_table() { + error!("Could not reset the clocks table: {err}"); + } + } + + if let Err(err) = controller.apply_config(&config::Gpu::default()).await { + error!("Could not reset settings for controller {id}: {err:#}"); + } + } + } +} diff --git a/lact-daemon/src/server/mod.rs b/lact-daemon/src/server/mod.rs new file mode 100644 index 00000000..ae5b9255 --- /dev/null +++ b/lact-daemon/src/server/mod.rs @@ -0,0 +1,148 @@ +pub mod gpu_controller; +pub mod handler; +// mod pci; +mod vulkan; + +use self::handler::Handler; +use crate::{config::Config, socket}; +use anyhow::Context; +use lact_schema::{Pong, Request, Response, SystemInfo}; +use serde::Serialize; +use std::{fs, process::Command}; +use tokio::{ + io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, + net::{UnixListener, UnixStream}, +}; +use tracing::{debug, error, instrument}; + +pub struct Server { + pub handler: Handler, + listener: UnixListener, +} + +impl Server { + pub async fn new(config: Config) -> anyhow::Result { + let listener = socket::listen(&config.daemon.admin_groups)?; + let handler = Handler::new(config).await?; + + Ok(Self { handler, listener }) + } + + pub async fn run(self) { + loop { + match self.listener.accept().await { + Ok((stream, _)) => { + let handler = self.handler.clone(); + tokio::spawn(async move { + if let Err(error) = handle_stream(stream, handler).await { + error!("{error}"); + } + }); + } + Err(error) => { + error!("failed to handle connection: {error}"); + } + } + } + } +} + +#[instrument(level = "debug", skip(stream, handler))] +pub async fn handle_stream(stream: UnixStream, handler: Handler) -> anyhow::Result<()> { + let mut stream = BufReader::new(stream); + + let mut buf = String::new(); + while stream.read_line(&mut buf).await? != 0 { + debug!("handling request: {}", buf.trim_end()); + + let maybe_request = serde_json::from_str(&buf); + let response = match maybe_request { + Ok(request) => match handle_request(request, &handler).await { + Ok(response) => response, + Err(error) => serde_json::to_vec(&Response::<()>::Error(format!("{error:#}")))?, + }, + Err(error) => serde_json::to_vec(&Response::<()>::Error(format!( + "Failed to deserialize request: {error}" + )))?, + }; + + stream.write_all(&response).await?; + stream.write_all(b"\n").await?; + + buf.clear(); + } + + Ok(()) +} + +#[instrument(level = "debug", skip(handler))] +async fn handle_request<'a>(request: Request<'a>, handler: &'a Handler) -> anyhow::Result> { + match request { + Request::Ping => ok_response(ping()), + Request::SystemInfo => ok_response(system_info()?), + Request::ListDevices => ok_response(handler.list_devices()), + Request::DeviceInfo { id } => ok_response(handler.get_device_info(id)?), + Request::DeviceStats { id } => ok_response(handler.get_gpu_stats(id)?), + Request::DeviceClocksInfo { id } => ok_response(handler.get_clocks_info(id)?), + Request::SetFanControl { id, enabled, curve } => { + ok_response(handler.set_fan_control(id, enabled, curve).await?) + } + Request::SetPowerCap { id, cap } => ok_response(handler.set_power_cap(id, cap).await?), + Request::SetPerformanceLevel { + id, + performance_level, + } => ok_response(handler.set_performance_level(id, performance_level).await?), + Request::SetClocksValue { id, command } => { + ok_response(handler.set_clocks_value(id, command).await?) + } + } +} + +fn ok_response(data: T) -> anyhow::Result> { + Ok(serde_json::to_vec(&Response::Ok(data))?) +} + +fn ping() -> Pong { + Pong +} + +fn system_info() -> anyhow::Result> { + let version = env!("CARGO_PKG_VERSION"); + let profile = if cfg!(debug_assertions) { + "debug" + } else { + "release" + }; + let kernel_output = Command::new("uname") + .arg("-r") + .output() + .context("Could not read kernel version")?; + let kernel_version = String::from_utf8(kernel_output.stdout) + .context("Invalid kernel version output")? + .trim() + .to_owned(); + + let amdgpu_overdrive_enabled = if let Ok(ppfeaturemask) = + fs::read_to_string("/sys/module/amdgpu/parameters/ppfeaturemask") + { + const PP_OVERDRIVE_MASK: i32 = 0x4000; + + let ppfeaturemask = ppfeaturemask + .trim() + .strip_prefix("0x") + .context("Invalid ppfeaturemask")?; + let ppfeaturemask: u64 = + u64::from_str_radix(ppfeaturemask, 16).context("Invalid ppfeaturemask")?; + + Some((ppfeaturemask & PP_OVERDRIVE_MASK as u64) > 0) + } else { + None + }; + + Ok(SystemInfo { + version, + profile, + kernel_version, + amdgpu_overdrive_enabled, + }) +} diff --git a/lact-daemon/src/server/vulkan.rs b/lact-daemon/src/server/vulkan.rs new file mode 100644 index 00000000..8b8f9434 --- /dev/null +++ b/lact-daemon/src/server/vulkan.rs @@ -0,0 +1,55 @@ +use std::borrow::Cow; + +use crate::fork::run_forked; +use lact_schema::{VulkanDriverInfo, VulkanInfo}; +use vulkano::{ + instance::{Instance, InstanceCreateInfo}, + VulkanLibrary, +}; + +pub fn get_vulkan_info<'a>(vendor_id: &'a str, device_id: &'a str) -> anyhow::Result { + let vendor_id = u32::from_str_radix(vendor_id, 16)?; + let device_id = u32::from_str_radix(device_id, 16)?; + + unsafe { + run_forked(|| { + let library = VulkanLibrary::new().map_err(|err| err.to_string())?; + let instance = Instance::new(library, InstanceCreateInfo::default()) + .map_err(|err| err.to_string())?; + let enabled_layers = instance.enabled_layers().to_vec(); + let devices = instance + .enumerate_physical_devices() + .map_err(|err| err.to_string())?; + + for device in devices { + let properties = device.properties(); + // Not sure how this works with systems that have multiple identical GPUs + if (properties.vendor_id, properties.device_id) == (vendor_id, device_id) { + let info = VulkanInfo { + device_name: properties.device_name.clone(), + api_version: device.api_version().to_string(), + driver: VulkanDriverInfo { + version: properties.driver_version, + name: properties.driver_name.clone(), + info: properties.driver_info.clone(), + }, + features: device + .supported_features() + .into_iter() + .map(|(name, enabled)| (Cow::Borrowed(name), enabled)) + .collect(), + extensions: device + .supported_extensions() + .into_iter() + .map(|(name, enabled)| (Cow::Borrowed(name), enabled)) + .collect(), + enabled_layers, + }; + return Ok(info); + } + } + + Err("Could not find a vulkan device with matching pci ids".to_owned()) + }) + } +} diff --git a/lact-daemon/src/socket.rs b/lact-daemon/src/socket.rs new file mode 100644 index 00000000..db1cc58f --- /dev/null +++ b/lact-daemon/src/socket.rs @@ -0,0 +1,67 @@ +use anyhow::anyhow; +use nix::{ + sys::stat::{umask, Mode}, + unistd::{chown, getuid, Gid, Group}, +}; +use std::{fs, path::PathBuf, str::FromStr}; +use tokio::net::UnixListener; +use tracing::{debug, info}; + +pub fn get_socket_path() -> PathBuf { + let uid = getuid(); + if uid.is_root() { + PathBuf::from_str("/var/run/lactd.sock").unwrap() + } else { + PathBuf::from_str(&format!("/var/run/user/{uid}/lactd.sock")).unwrap() + } +} + +pub fn cleanup() { + let socket_path = get_socket_path(); + + if socket_path.exists() { + fs::remove_file(socket_path).expect("failed to remove socket"); + } + debug!("removed socket"); +} + +pub fn listen(admin_groups: &[String]) -> anyhow::Result { + let socket_path = get_socket_path(); + + if socket_path.exists() { + return Err(anyhow!( + "Socket {socket_path:?} already exists. \ + This probably means that another instance of lact-daemon is currently running. \ + If you are sure that this is not the case, please remove the file" + )); + } + + let socket_mask = Mode::S_IXUSR | Mode::S_IXGRP | Mode::S_IRWXO; + umask(socket_mask); + + let listener = UnixListener::bind(&socket_path)?; + + chown(&socket_path, None, Some(socket_gid(admin_groups)))?; + + info!("listening on {socket_path:?}"); + Ok(listener) +} + +fn socket_gid(admin_groups: &[String]) -> Gid { + if getuid().is_root() { + // Check if the group exists + for group_name in admin_groups { + if let Ok(Some(group)) = Group::from_name(group_name) { + return group.gid; + } + } + + if let Ok(Some(group)) = Group::from_gid(Gid::from_raw(1000)) { + group.gid + } else { + Gid::current() + } + } else { + Gid::current() + } +} diff --git a/lact-gui/Cargo.toml b/lact-gui/Cargo.toml new file mode 100644 index 00000000..638b11de --- /dev/null +++ b/lact-gui/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "lact-gui" +version = "0.2.0" +authors = ["Ilya Zlobintsev "] +edition = "2021" + +[features] +default = ["gtk-tests"] +gtk-tests = [] + +[dependencies] +lact-client = { path = "../lact-client" } +lact-daemon = { path = "../lact-daemon" } +gtk = { version = "0.6", package = "gtk4", features = ["v4_6"] } +once_cell = "1.17.1" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +anyhow = "1.0" + +[dev-dependencies] +pretty_assertions = "1.3.0" diff --git a/lact-gui/src/app/apply_revealer.rs b/lact-gui/src/app/apply_revealer.rs new file mode 100644 index 00000000..dd531851 --- /dev/null +++ b/lact-gui/src/app/apply_revealer.rs @@ -0,0 +1,50 @@ +use gtk::prelude::*; +use gtk::*; + +#[derive(Clone)] +pub struct ApplyRevealer { + pub container: Revealer, + apply_button: Button, + reset_button: Button, +} + +impl ApplyRevealer { + pub fn new() -> Self { + let container = Revealer::builder().transition_duration(150).build(); + let vbox = Box::new(Orientation::Horizontal, 5); + + let apply_button = Button::builder().label("Apply").hexpand(true).build(); + let reset_button = Button::builder().label("Reset").build(); + + vbox.append(&apply_button); + vbox.append(&reset_button); + + container.set_child(Some(&vbox)); + + Self { + container, + apply_button, + reset_button, + } + } + + pub fn show(&self) { + self.container.set_reveal_child(true); + } + + pub fn hide(&self) { + self.container.set_reveal_child(false); + } + + pub fn connect_apply_button_clicked(&self, f: F) { + self.apply_button.connect_clicked(move |_| { + f(); + }); + } + + pub fn connect_reset_button_clicked(&self, f: F) { + self.reset_button.connect_clicked(move |_| { + f(); + }); + } +} diff --git a/gui/src/app/header.rs b/lact-gui/src/app/header.rs similarity index 58% rename from gui/src/app/header.rs rename to lact-gui/src/app/header.rs index 7a4add61..e6498991 100644 --- a/gui/src/app/header.rs +++ b/lact-gui/src/app/header.rs @@ -1,7 +1,7 @@ use gtk::prelude::*; use gtk::*; +use lact_client::schema::DeviceListEntry; use pango::EllipsizeMode; -use std::collections::HashMap; #[derive(Clone)] pub struct Header { @@ -14,9 +14,10 @@ impl Header { pub fn new() -> Self { let container = HeaderBar::new(); - container.set_custom_title(Some(&Grid::new())); // Bad workaround to hide the title + // TODO Check if this is this still needed + container.set_title_widget(Some(&Grid::new())); // Bad workaround to hide the title - container.set_show_close_button(true); + container.set_show_title_buttons(true); let gpu_selector = ComboBoxText::new(); container.pack_start(&gpu_selector); @@ -35,25 +36,26 @@ impl Header { self.switcher.set_stack(Some(stack)); } - pub fn set_gpus(&self, gpus: HashMap>) { - for (id, name) in &gpus { + pub fn set_devices(&self, gpus: &[DeviceListEntry<'_>]) { + for entry in gpus { self.gpu_selector - .append(Some(&id.to_string()), &name.clone().unwrap_or_default()); + .append(Some(entry.id), entry.name.unwrap_or_default()); } //limits the length of gpu names in combobox for cell in self.gpu_selector.cells() { - cell.set_property("width-chars", &10).unwrap(); - cell.set_property("ellipsize", &EllipsizeMode::End).unwrap(); + cell.set_property("width-chars", &10); + cell.set_property("ellipsize", &EllipsizeMode::End); } self.gpu_selector.set_active(Some(0)); } - pub fn connect_gpu_selection_changed(&self, f: F) { + pub fn connect_gpu_selection_changed(&self, f: F) { self.gpu_selector.connect_changed(move |gpu_selector| { - let selected_id = gpu_selector.active_id().unwrap(); - f(selected_id.parse().unwrap()); + if let Some(selected_id) = gpu_selector.active_id() { + f(selected_id.to_string()); + } }); } } diff --git a/lact-gui/src/app/mod.rs b/lact-gui/src/app/mod.rs new file mode 100644 index 00000000..962c3ff9 --- /dev/null +++ b/lact-gui/src/app/mod.rs @@ -0,0 +1,321 @@ +mod apply_revealer; +mod header; +mod root_stack; + +use crate::APP_ID; +use anyhow::{anyhow, Context}; +use apply_revealer::ApplyRevealer; +use glib::clone; +use gtk::{gio::ApplicationFlags, prelude::*, *}; +use header::Header; +use lact_client::schema::request::SetClocksCommand; +use lact_client::schema::DeviceStats; +use lact_client::DaemonClient; +use root_stack::RootStack; +use std::sync::{Arc, RwLock}; +use std::thread; +use std::time::Duration; +use tracing::{debug, error, trace, warn}; + +// In ms +const STATS_POLL_INTERVAL: u64 = 250; + +#[derive(Clone)] +pub struct App { + application: Application, + pub window: ApplicationWindow, + pub header: Header, + root_stack: RootStack, + apply_revealer: ApplyRevealer, + daemon_client: DaemonClient, +} + +impl App { + pub fn new(daemon_client: DaemonClient) -> Self { + let application = Application::new(Some(APP_ID), ApplicationFlags::default()); + + let header = Header::new(); + let window = ApplicationWindow::builder() + .title("LACT") + .default_width(500) + .default_height(600) + .icon_name(APP_ID) + .build(); + + window.set_titlebar(Some(&header.container)); + + let system_info_buf = daemon_client + .get_system_info() + .expect("Could not fetch system info"); + let system_info = system_info_buf.inner().expect("Invalid system info buffer"); + let root_stack = RootStack::new(system_info, daemon_client.embedded); + + header.set_switcher_stack(&root_stack.container); + + let root_box = Box::new(Orientation::Vertical, 5); + + root_box.append(&root_stack.container); + + let apply_revealer = ApplyRevealer::new(); + + root_box.append(&apply_revealer.container); + + window.set_child(Some(&root_box)); + + App { + application, + window, + header, + root_stack, + apply_revealer, + daemon_client, + } + } + + pub fn run(self) -> anyhow::Result<()> { + self.application + .connect_activate(clone!(@strong self as app => move |_| { + app.window.set_application(Some(&app.application)); + + let current_gpu_id = Arc::new(RwLock::new(String::new())); + + + app.header.connect_gpu_selection_changed(clone!(@strong app, @strong current_gpu_id => move |gpu_id| { + debug!("GPU Selection changed"); + app.set_info(&gpu_id); + *current_gpu_id.write().unwrap() = gpu_id; + })); + + let devices_buf = app + .daemon_client + .list_devices() + .expect("Could not list devices"); + let devices = devices_buf.inner().expect("Could not access devices"); + app.header.set_devices(&devices); + + + app.root_stack.oc_page.clocks_frame.connect_clocks_reset(clone!(@strong app, @strong current_gpu_id => move || { + debug!("Resetting clocks"); + + let gpu_id = current_gpu_id.read().unwrap(); + + match app.daemon_client.set_clocks_value(&gpu_id, SetClocksCommand::Reset) { + Ok(()) => { + app.set_initial(&gpu_id); + } + Err(err) => { + show_error(&app.window, err); + } + } + })); + + app.apply_revealer.connect_apply_button_clicked( + clone!(@strong app, @strong current_gpu_id => move || { + if let Err(err) = app.apply_settings(current_gpu_id.clone()) { + show_error(&app.window, err.context("Could not apply settings")); + + glib::idle_add_local_once(clone!(@strong app, @strong current_gpu_id => move || { + let gpu_id = current_gpu_id.read().unwrap(); + app.set_initial(&gpu_id) + })); + } + }), + ); + app.apply_revealer.connect_reset_button_clicked(clone!(@strong app, @strong current_gpu_id => move || { + let gpu_id = current_gpu_id.read().unwrap(); + app.set_initial(&gpu_id) + })); + + app.start_stats_update_loop(current_gpu_id); + + app.window.show(); + + if app.daemon_client.embedded { + show_error(&app.window, anyhow!( + "Could not connect to daemon, running in embedded mode. \n\ + Please make sure the lactd service is running. \n\ + Using embedded mode, you will not be able to change any settings." + )); + } + })); + + // Args are passed manually since they were already processed by clap before + self.application.run_with_args::(&[]); + Ok(()) + } + + fn set_info(&self, gpu_id: &str) { + let info_buf = self + .daemon_client + .get_device_info(gpu_id) + .expect("Could not fetch info"); + let info = info_buf.inner().unwrap(); + + trace!("setting info {info:?}"); + + self.root_stack.info_page.set_info(&info); + + self.set_initial(gpu_id); + } + + fn set_initial(&self, gpu_id: &str) { + let stats_buf = self + .daemon_client + .get_device_stats(gpu_id) + .expect("Could not fetch stats"); + let stats = stats_buf.inner().unwrap(); + + self.root_stack.oc_page.set_stats(&stats, true); + self.root_stack.thermals_page.set_stats(&stats, true); + + let maybe_clocks_table = match self.daemon_client.get_device_clocks_info(gpu_id) { + Ok(clocks_buf) => match clocks_buf.inner() { + Ok(info) => info.table, + Err(err) => { + debug!("could not extract clocks info: {err:?}"); + None + } + }, + Err(err) => { + debug!("could not fetch clocks info: {err:?}"); + None + } + }; + self.root_stack.oc_page.set_clocks_table(maybe_clocks_table); + + // Show apply button on setting changes + // This is done here because new widgets may appear after applying settings (like fan curve points) which should be connected + let show_revealer = clone!(@strong self.apply_revealer as apply_revealer => move || { + debug!("settings changed, showing apply button"); + apply_revealer.show(); + }); + + self.root_stack + .thermals_page + .connect_settings_changed(show_revealer.clone()); + + self.root_stack + .oc_page + .connect_settings_changed(show_revealer); + + self.apply_revealer.hide(); + } + + fn start_stats_update_loop(&self, current_gpu_id: Arc>) { + let context = glib::MainContext::default(); + + let _guard = context.acquire(); + + // The loop that gets stats + let (sender, receiver) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + + thread::spawn( + clone!(@strong self.daemon_client as daemon_client => move || loop { + let gpu_id = current_gpu_id.read().unwrap(); + match daemon_client + .get_device_stats(&gpu_id) + .and_then(|stats| stats.inner()) + { + Ok(stats) => { + sender.send(GuiUpdateMsg::GpuStats(stats)).unwrap(); + } + Err(err) => { + error!("Could not fetch stats: {err}"); + } + } + thread::sleep(Duration::from_millis(STATS_POLL_INTERVAL)); + }), + ); + + // Receiving stats into the gui event loop + + receiver.attach( + None, + clone!(@strong self.root_stack as root_stack => move |msg| { + match msg { + GuiUpdateMsg::GpuStats(stats) => { + trace!("new stats received, updating {stats:?}"); + root_stack.info_page.set_stats(&stats); + root_stack.thermals_page.set_stats(&stats, false); + root_stack.oc_page.set_stats(&stats, false); + } + } + + glib::Continue(true) + }), + ); + } + + fn apply_settings(&self, current_gpu_id: Arc>) -> anyhow::Result<()> { + debug!("applying settings"); + + let gpu_id = current_gpu_id.read().unwrap(); + + if let Some(cap) = self.root_stack.oc_page.get_power_cap() { + self.daemon_client + .set_power_cap(&gpu_id, Some(cap)) + .context("Failed to set power cap")?; + } + + if let Some(level) = self.root_stack.oc_page.get_performance_level() { + self.daemon_client + .set_performance_level(&gpu_id, level) + .context("Failed to set power profile")?; + } + + if let Some(thermals_settings) = self.root_stack.thermals_page.get_thermals_settings() { + debug!("applying thermal settings: {thermals_settings:?}"); + + self.daemon_client + .set_fan_control( + &gpu_id, + thermals_settings.manual_fan_control, + thermals_settings.curve, + ) + .context("Could not set fan control")?; + } + + let clocks_settings = self.root_stack.oc_page.clocks_frame.get_settings(); + + if let Some(clock) = clocks_settings.max_core_clock { + self.daemon_client + .set_clocks_value(&gpu_id, SetClocksCommand::MaxCoreClock(clock)) + .context("Could not set the maximum core clock")?; + } + + if let Some(clock) = clocks_settings.max_memory_clock { + self.daemon_client + .set_clocks_value(&gpu_id, SetClocksCommand::MaxMemoryClock(clock)) + .context("Could not set the maximum memory clock")?; + } + + if let Some(voltage) = clocks_settings.max_voltage { + self.daemon_client + .set_clocks_value(&gpu_id, SetClocksCommand::MaxVoltage(voltage)) + .context("Could not set the maximum voltage")?; + } + + self.set_initial(&gpu_id); + + Ok(()) + } +} + +enum GuiUpdateMsg { + GpuStats(DeviceStats), +} + +fn show_error(parent: &ApplicationWindow, err: anyhow::Error) { + let text = format!("{err:?}"); + warn!("{}", text.trim()); + let diag = MessageDialog::builder() + .title("Error") + .message_type(MessageType::Error) + .text(&text) + .buttons(ButtonsType::Close) + .transient_for(parent) + .build(); + diag.run_async(|diag, _| { + diag.hide(); + }) +} diff --git a/gui/src/app/root_stack/info_page.rs b/lact-gui/src/app/root_stack/info_page/mod.rs similarity index 55% rename from gui/src/app/root_stack/info_page.rs rename to lact-gui/src/app/root_stack/info_page/mod.rs index 62c98d41..af6deeeb 100644 --- a/gui/src/app/root_stack/info_page.rs +++ b/lact-gui/src/app/root_stack/info_page/mod.rs @@ -1,8 +1,8 @@ mod vulkan_info; -use daemon::gpu_controller::GpuInfo; use gtk::prelude::*; use gtk::*; +use lact_client::schema::{DeviceInfo, DeviceStats}; use vulkan_info::VulkanInfoFrame; #[derive(Clone)] @@ -31,6 +31,13 @@ impl InformationPage { container.set_row_spacing(7); container.set_column_spacing(5); + // Dummy label to prevent the gpu name label from stealing focus + let dummy_label = Label::builder() + .selectable(true) + .halign(Align::Start) + .build(); + container.attach(&dummy_label, 0, 0, 1, 1); + container.attach( &{ let label = Label::new(Some("GPU Model:")); @@ -43,9 +50,7 @@ impl InformationPage { 1, ); - let gpu_name_label = Label::new(None); - gpu_name_label.set_halign(Align::Start); - + let gpu_name_label = value_label(); container.attach(&gpu_name_label, 2, 0, 3, 1); container.attach( @@ -60,9 +65,7 @@ impl InformationPage { 1, ); - let gpu_manufacturer_label = Label::new(None); - gpu_manufacturer_label.set_halign(Align::Start); - + let gpu_manufacturer_label = value_label(); container.attach(&gpu_manufacturer_label, 2, 1, 3, 1); container.attach( @@ -77,9 +80,7 @@ impl InformationPage { 1, ); - let vbios_version_label = Label::new(None); - vbios_version_label.set_halign(Align::Start); - + let vbios_version_label = value_label(); container.attach(&vbios_version_label, 2, 2, 3, 1); container.attach( @@ -94,9 +95,7 @@ impl InformationPage { 1, ); - let driver_label = Label::new(None); - driver_label.set_halign(Align::Start); - + let driver_label = value_label(); container.attach(&driver_label, 2, 3, 3, 1); container.attach( @@ -111,9 +110,7 @@ impl InformationPage { 1, ); - let vram_size_label = Label::new(None); - vram_size_label.set_halign(Align::Start); - + let vram_size_label = value_label(); container.attach(&vram_size_label, 2, 4, 3, 1); container.attach( @@ -128,7 +125,7 @@ impl InformationPage { 1, ); - let link_speed_label = Label::new(None); + let link_speed_label = value_label(); link_speed_label.set_halign(Align::Start); container.attach(&link_speed_label, 2, 5, 3, 1); @@ -148,31 +145,73 @@ impl InformationPage { } } - pub fn set_info(&self, gpu_info: &GpuInfo) { - self.gpu_name_label.set_markup(&format!( - "{}", - match &gpu_info.vendor_data.card_model { - Some(card_model) => card_model.clone(), - None => gpu_info.vendor_data.gpu_model.clone().unwrap_or_default(), - } - )); - self.gpu_manufacturer_label.set_markup(&format!( - "{}", - gpu_info.vendor_data.card_vendor.clone().unwrap_or_default() - )); + pub fn set_info(&self, gpu_info: &DeviceInfo) { + let gpu_name = gpu_info + .pci_info + .as_ref() + .and_then(|pci_info| { + pci_info + .subsystem_pci_info + .model + .as_deref() + .or(pci_info.device_pci_info.model.as_deref()) + }) + .unwrap_or_default(); + self.gpu_name_label + .set_markup(&format!("{gpu_name}",)); + + let gpu_manufacturer = gpu_info + .pci_info + .as_ref() + .and_then(|pci_info| { + pci_info + .subsystem_pci_info + .vendor + .as_deref() + .or(pci_info.device_pci_info.model.as_deref()) + }) + .unwrap_or_default(); + self.gpu_manufacturer_label + .set_markup(&format!("{gpu_manufacturer}",)); + + let vbios_version = gpu_info.vbios_version.as_deref().unwrap_or("Unknown"); self.vbios_version_label - .set_markup(&format!("{}", gpu_info.vbios_version)); + .set_markup(&format!("{vbios_version}",)); + self.driver_label .set_markup(&format!("{}", gpu_info.driver)); - self.vram_size_label - .set_markup(&format!("{}", gpu_info.vram_size)); - self.link_speed_label.set_markup(&format!( - "{} x{}", - gpu_info.link_speed, gpu_info.link_width - )); - self.vulkan_info_frame.set_info(&gpu_info.vulkan_info); + let link_speed = gpu_info + .link_info + .current_speed + .as_deref() + .unwrap_or("Unknown"); + let link_width = gpu_info + .link_info + .current_width + .as_deref() + .unwrap_or("Unknown"); + self.link_speed_label + .set_markup(&format!("{link_speed} x{link_width}",)); + + if let Some(vulkan_info) = &gpu_info.vulkan_info { + self.vulkan_info_frame.set_info(vulkan_info); + } + } - self.container.show_all(); + pub fn set_stats(&self, stats: &DeviceStats) { + let vram_size = stats.vram.total.map_or_else( + || "Unknown".to_owned(), + |size| (size / 1024 / 1024).to_string(), + ); + self.vram_size_label + .set_markup(&format!("{vram_size} MiB")); } } + +fn value_label() -> Label { + Label::builder() + .selectable(true) + .halign(Align::Start) + .build() +} diff --git a/lact-gui/src/app/root_stack/info_page/vulkan_info/feature_model/imp.rs b/lact-gui/src/app/root_stack/info_page/vulkan_info/feature_model/imp.rs new file mode 100644 index 00000000..b7ef7b04 --- /dev/null +++ b/lact-gui/src/app/root_stack/info_page/vulkan_info/feature_model/imp.rs @@ -0,0 +1,54 @@ +use gio::subclass::prelude::*; +use gtk::{ + gio, + glib::{self, ParamSpec, ParamSpecBoolean, ParamSpecString}, + prelude::*, +}; +use once_cell::sync::Lazy; +use std::cell::{Cell, RefCell}; + +#[derive(Debug, Default)] +pub struct FeatureModel { + pub name: RefCell, + pub supported: Cell, +} + +#[glib::object_subclass] +impl ObjectSubclass for FeatureModel { + const NAME: &'static str = "VulkanFeatureModel"; + type Type = super::FeatureModel; +} + +impl ObjectImpl for FeatureModel { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![ + ParamSpecString::builder("name").build(), + ParamSpecBoolean::builder("supported").build(), + ] + }); + PROPERTIES.as_ref() + } + + fn set_property(&self, _id: usize, value: &glib::Value, pspec: &ParamSpec) { + match pspec.name() { + "name" => { + let name = value.get().expect("Name needs to be a string"); + self.name.replace(name); + } + "supported" => { + let supported = value.get().expect("Supported needs to be a bool"); + self.supported.replace(supported); + } + _ => unimplemented!(), + } + } + + fn property(&self, _id: usize, pspec: &ParamSpec) -> glib::Value { + match pspec.name() { + "name" => self.name.borrow().to_value(), + "supported" => self.supported.get().to_value(), + _ => unimplemented!(), + } + } +} diff --git a/lact-gui/src/app/root_stack/info_page/vulkan_info/feature_model/mod.rs b/lact-gui/src/app/root_stack/info_page/vulkan_info/feature_model/mod.rs new file mode 100644 index 00000000..a7cbc49b --- /dev/null +++ b/lact-gui/src/app/root_stack/info_page/vulkan_info/feature_model/mod.rs @@ -0,0 +1,16 @@ +mod imp; + +use gtk::glib::{self, Object}; + +glib::wrapper! { + pub struct FeatureModel(ObjectSubclass); +} + +impl FeatureModel { + pub fn new(name: String, supported: bool) -> Self { + Object::builder() + .property("name", name) + .property("supported", supported) + .build() + } +} diff --git a/lact-gui/src/app/root_stack/info_page/vulkan_info/mod.rs b/lact-gui/src/app/root_stack/info_page/vulkan_info/mod.rs new file mode 100644 index 00000000..10b24e79 --- /dev/null +++ b/lact-gui/src/app/root_stack/info_page/vulkan_info/mod.rs @@ -0,0 +1,237 @@ +mod feature_model; + +use self::feature_model::FeatureModel; +use super::value_label; +use glib::clone; +use gtk::prelude::*; +use gtk::*; +use lact_client::schema::VulkanInfo; +use std::cell::RefCell; +use std::rc::Rc; +use tracing::trace; + +#[derive(Clone)] +pub struct VulkanInfoFrame { + pub container: Frame, + device_name_label: Label, + version_label: Label, + features: Rc>>, + extensions: Rc>>, +} + +impl VulkanInfoFrame { + pub fn new() -> Self { + let container = Frame::new(None); + + container.set_label_widget(Some(&{ + let label = Label::new(None); + label.set_markup("Vulkan Information"); + label + })); + container.set_label_align(0.5); + + let features = Rc::new(RefCell::new(Vec::new())); + let extensions = Rc::new(RefCell::new(Vec::new())); + + let vbox = Box::new(Orientation::Vertical, 5); + + let grid = Grid::new(); + + grid.set_margin_start(5); + grid.set_margin_end(5); + grid.set_margin_bottom(5); + grid.set_margin_top(5); + + grid.set_column_homogeneous(true); + grid.set_row_homogeneous(false); + + grid.set_row_spacing(7); + grid.set_column_spacing(5); + + grid.attach( + &{ + let label = Label::new(Some("Device name:")); + label.set_halign(Align::End); + label + }, + 0, + 0, + 2, + 1, + ); + + let device_name_label = value_label(); + grid.attach(&device_name_label, 2, 0, 3, 1); + + grid.attach( + &{ + let label = Label::new(Some("Version:")); + label.set_halign(Align::End); + label + }, + 0, + 1, + 2, + 1, + ); + + let version_label = value_label(); + grid.attach(&version_label, 2, 1, 3, 1); + + let features_label = Label::builder() + .label("Features:") + .halign(Align::End) + .build(); + let show_features_button = Button::builder().label("Show").halign(Align::Start).build(); + show_features_button.connect_clicked(clone!(@strong features => move |_| { + show_list_window("Vulkan features", &features.borrow()); + })); + + grid.attach(&features_label, 0, 2, 2, 1); + grid.attach(&show_features_button, 2, 2, 2, 1); + + let extensions_label = Label::builder() + .label("Extensions:") + .halign(Align::End) + .build(); + let show_extensions_button = Button::builder().label("Show").halign(Align::Start).build(); + show_extensions_button.connect_clicked(clone!(@strong extensions => move |_| { + show_list_window("Vulkan extensions", &extensions.borrow()); + })); + + grid.attach(&extensions_label, 0, 3, 2, 1); + grid.attach(&show_extensions_button, 2, 3, 2, 1); + + vbox.prepend(&grid); + + container.set_child(Some(&vbox)); + + Self { + container, + device_name_label, + version_label, + features, + extensions, + } + } + + pub fn set_info(&self, vulkan_info: &VulkanInfo) { + trace!("setting vulkan info: {:?}", vulkan_info); + + self.device_name_label + .set_markup(&format!("{}", vulkan_info.device_name)); + self.version_label + .set_markup(&format!("{}", vulkan_info.api_version)); + + let features_vec: Vec<_> = vulkan_info + .features + .iter() + .map(|(name, supported)| FeatureModel::new(name.to_string(), *supported)) + .collect(); + self.features.replace(features_vec); + + let extensions_vec: Vec<_> = vulkan_info + .extensions + .iter() + .map(|(name, supported)| FeatureModel::new(name.to_string(), *supported)) + .collect(); + self.extensions.replace(extensions_vec); + } +} + +fn show_list_window(title: &str, items: &[FeatureModel]) { + let window = Window::builder() + .title(title) + .width_request(500) + .height_request(700) + .build(); + + let base_model = gio::ListStore::new(FeatureModel::static_type()); + base_model.extend_from_slice(items); + + let expression = PropertyExpression::new(FeatureModel::static_type(), Expression::NONE, "name"); + let filter = StringFilter::builder() + .match_mode(StringFilterMatchMode::Substring) + .ignore_case(true) + .expression(expression) + .build(); + + let entry = SearchEntry::builder().hexpand(true).build(); + entry.connect_search_changed(clone!(@weak filter => move |entry| { + if entry.text().is_empty() { + filter.set_search(None); + } else { + filter.set_search(Some(entry.text().as_str())); + } + })); + let search_bar = SearchBar::builder() + .child(&entry) + .search_mode_enabled(true) + .key_capture_widget(&window) + .build(); + + let filter_model = FilterListModel::builder() + .model(&base_model) + .filter(&filter) + .incremental(true) + .build(); + + let selection_model = NoSelection::new(Some(filter_model)); + + let factory = gtk::SignalListItemFactory::new(); + + factory.connect_setup(move |_factory, item| { + let item = item.downcast_ref::().unwrap(); + let label = Label::builder() + .margin_top(5) + .margin_bottom(5) + .selectable(true) + .hexpand(true) + .halign(Align::Start) + .build(); + let image = Image::new(); + let vbox = Box::builder() + .orientation(Orientation::Horizontal) + .spacing(5) + .margin_start(10) + .margin_end(10) + .build(); + vbox.append(&label); + vbox.append(&image); + item.set_child(Some(&vbox)); + }); + + factory.connect_bind(move |_factory, item| { + let item = item.downcast_ref::().unwrap(); + let model = item.item().and_downcast::().unwrap(); + + let vbox = item.child().and_downcast::().unwrap(); + let children = vbox.observe_children(); + let label = children.item(0).and_downcast::