Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Factory:Rebuild
supergfxctl
supergfxctl-5.2.4.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File supergfxctl-5.2.4.obscpio of Package supergfxctl
07070100000000000081A400000000000000000000000166FDDEC800000049000000000000000000000000000000000000001D00000000supergfxctl-5.2.4/.gitignore/target vendor.tar.xz cargo-config .idea vendor-* vendor_* .vscode-ctags 07070100000001000041ED00000000000000000000000266FDDEC800000000000000000000000000000000000000000000001A00000000supergfxctl-5.2.4/.gitlab07070100000002000081A400000000000000000000000166FDDEC8000001DC000000000000000000000000000000000000002100000000supergfxctl-5.2.4/.gitlab-ci.ymlimage: rust:latest before_script: - apt-get update -qq && apt-get install -y -qq libdbus-1-dev libclang-dev libudev-dev stages: - test - build test: script: - rustup component add clippy - cargo check - cargo clippy - cargo test build: only: - main script: - cargo install cargo-vendor-filterer - make && make vendor artifacts: paths: - vendor-*.tar.xz - cargo-config variables: GIT_SUBMODULE_STRATEGY: normal 07070100000003000041ED00000000000000000000000266FDDEC800000000000000000000000000000000000000000000002A00000000supergfxctl-5.2.4/.gitlab/issue_templates07070100000004000081A400000000000000000000000166FDDEC800000223000000000000000000000000000000000000003500000000supergfxctl-5.2.4/.gitlab/issue_templates/default.md## Issue description (Summarize the bug encountered) ## Steps to reproduce (How can the issue be reproduced) ## What is the current bug behavior? (What actually happens) ## What is the expected correct behavior? (What you should see instead) ## Relevant logs and/or screenshots (run `journalctl -b -u supergfxd > ~/supergfxd.log` and attach `~/supergfxd.log`) (Paste any relevant logs - use code blocks (```) to format console output, logs, and code, as it's very hard to read otherwise.) /label ~bug ~reproducable ~needs-investigation 07070100000005000081A400000000000000000000000166FDDEC800001BD9000000000000000000000000000000000000001F00000000supergfxctl-5.2.4/CHANGELOG.md# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ## [5.243] - 2024-10-03 ### Changed - Bump deps ## [5.2.3] - 2024-05-06 ### Changed - Minor adjustsments to asus egpu safety logic - Add missing mode/string conversions - Bump deps ## [5.2.1] - 2024-03-17 ### Changed - Better sanity check for booting without egpu after it was previously set ### Added - Vulkan ICD profile switching (thanks Armas Spann) ## [5.1.2] - 2023-09-07 ### Changed - Fix Asus disable_dgpu mode re-enable of dgpu - Fix asus egpu switching - Fix writing the correct modprobe if switching from Integrated to Hybrid when Asus hotplug_mode is enabled - Update zbus deps ## [5.1.1] - 2023-4-26 ### Changed - Adjust the internal action list for VFIO mode ## [5.1.0] - 2023-4-23 ### Notes: - The ASUS Egpu is still in a state of testing. It works, but you must plug it in and flick the switch before changing modes. - The ASUS MUX toggle always requires a reboot due to how it works internally in the ACPI. The iGPU may still be available. - If you have an encrypted disk you may need to bliindly enter your password. A black screen does not always mean boot failed, it's an artifact of kernel boot plus this MUX. - ASUS dgpu_disable is able to be set to be used for Integrated, but it may or may not work well for the same reasons as egpu above. - If you dual boot with Windows then the states of dgpu_disable, egpu_enable, and gpu_mux_mode should be picked up by supergfxd and the OS put in the right mode - vice versa for Windows. ### Changed - Add "Display" to GfxMode - Add "Display" to GfxRequiredUserAction - Refactor architecture to actions plus action lists that are dependant on which mode is booted and which mode switching to/from - Refactor asus egpu handling - Add a "NvidiaNoModeset" mode specially for machines like the GA401I series - Enable ASUS MUX control - Better boot safety checks of dgpu_disable, egpu_enable, gpu_mux_mode - Adjust boot actions for asus egpu ### **Breaking** - Dbus args for get/set mode changed to: - Hybrid, - Integrated, - NvidiaNoModeset, - Vfio, - AsusEgpu, - AsusMuxDgpu, - None, ## [5.0.1] - 2022-11-03 ### Changed - Rmeoved Compute mode - Added a check in asus_reload to help GA401I and older laptops - Add notify gfx power status to dbus ## [5.0.0] - 2022-10-21 ### Added - 99-nvidia-ac.rules udev rule added to ./data, this rule is useful for stopping `nvidia-powerd` on battery as some nvidia based laptops are poorly behaved when it is active (OPTIONAL) - New config option: `hotplug_type`. This accepts: None (default), Std, or Asus. Std tries to use the kernel hotplug mechanism if available, while Asus tries to use dgpu_disable if available - With the above, your success with with Std or Asus may vary, and may be unreliable. In general try Std first and check the battery drain after toggling. ### Changed - nvidia.modeset=0 not required for rebootless switching now - Removed dedicated mode as it causes more trouble than it is worth (ASUS: use gpu_mux_mode patch or kernel 6.1) - Better support for ASUS dgpu_disable and egpu_enable - Ensure sufficient time passes before rescan on dgpu_disable change - Check if asus gpu_mux_mode exists, and value of it - Fix: add logind sleep/resume task to ensure dgpu_disable is set (only if hotplug_type == Asus) - Using udev internally to find devices rather than manually scanning directories - Cleaner systemd systemctl interaction - Try to ignore session zbus error on path object not existing - Rework dgpu detection - Refactor ordering of device ops - Retry mode change thread on first fail - Remove the hotplug prep thing - Change some &str args to enum + From<T> impl - **Please review README.md for further info** ## [4.0.5] - 2022-06-22 ### Changed - Fix interaction with lspci >= 3.8.0 (Author: Anton Shangareev) - add "Quadro" to the lspci parsing for NVIDIA cards (Author: Brandon Bennett) ## [4.0.4] - 2022-02-05 ### Changed - Adjust the kernel cmdline arg code path ## [4.0.3] - 2022-02-04 ### Added - Add config option `no_logind`: Don't use logind to see if all sessions are logged out and therefore safe to change mode. This will be useful for people not using a login manager, however it is not guaranteed to work unless all graphical sessions are ended and nothing is hooking the drivers. Ignored if `always_reboot` is set. - Add config option `logout_timeout_s`: The timeout in seconds to wait for all user graphical sessions to end. Default is 3 minutes, 0 = infinite. Ignored if `no_logind` or `always_reboot` is set. - Add new dbus method: `PendingMode`, to check if a mode change is required - Add new dbus method: `PendingUserAction`, to check if the user is required to perform an action - Add new dbus method: `Config`, to get the current base config - Add new dbus method: `SetConfig`, to set the base config - Add `-p, --pend-action` CLI arg to get the pending user action if any - Add `-P, --pend-mode` CLI arg to get the pending mode change if any` - Add ability to read `supergfxd.mode=` from kernel cmdline on startup and set the mode appropriately ### Removed - CLI option `--force` was unused, it is now removed. ## [4.0.2] - 2022-01-22 ### Changed - Adjust how xorg config is created so that EGPU mode uses it also ## [4.0.1] - 2022-01-20 ### Changed - Fix version upgrade of config - Recreate the config if parsing fails - Only write the mode change to config file, don't update live config ### Added - AMD dedicated + hybrid config for xorg - "AllowExternalGpus" added to xorg for Nvidia Egpu mode ## [4.0.0] - 2022-01-18 ### Added - Add new dbus method: `Version` to get supergfxd version - Add new dbus method: `Vendor` to get dGPU vendor name - Add new dbus method: `Supported` to get list of supported modes - Add `-v, --version` CLI arg to get supergfxd version - Add `-V, --vendor` CLI arg to get dGPU vendor name - Add `-s, --supported` CLI arg to get list of supported modes - Add new config option: `vfio_save` to reload VFIO on boot - Add new config option: `compute_save` to reload compute on boot - Add new config option: `always_reboot` reboot to change modes ### Changed - Adjust startup to check for ASUS eGPU and dGPU enablement if no modes supported - If nvidia-drm.modeset=1 is set then save mode and require a reboot by default\ - Add extra check for Nvidia dGPU (fixes Flow 13") - Properly check the correct device for power status ### Breaking - Rename Vendor, GetVendor to Mode, GetMode to better reflect their results ## [3.0.0] - 2022-01-10 ### Added - Keep a changelog ### Changed - Support laptops with AMD dGPU + `hybrid`, `integrated`, `vfio` only + Modes unsupported by AMD dGPU will return an error - `nvidia` mode is now `dedicated` - Don't write the config twice on laptops with hard-mux switch - CLI print zbus error string if available - Heavy internal cleanup and refactor to make the project a bit nicer to work with07070100000006000081A400000000000000000000000166FDDEC800009522000000000000000000000000000000000000001D00000000supergfxctl-5.2.4/Cargo.lock# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "async-broadcast" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" dependencies = [ "event-listener", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-channel" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "slab", ] [[package]] name = "async-fs" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a" dependencies = [ "async-lock", "blocking", "futures-lite", ] [[package]] name = "async-io" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", "futures-lite", "parking", "polling", "rustix", "slab", "tracing", "windows-sys 0.59.0", ] [[package]] name = "async-lock" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ "event-listener", "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-process" version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a07789659a4d385b79b18b9127fc27e1a59e1e89117c78c5ea3b806f016374" dependencies = [ "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", "cfg-if", "event-listener", "futures-lite", "rustix", "tracing", "windows-sys 0.59.0", ] [[package]] name = "async-recursion" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "async-signal" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" dependencies = [ "async-io", "async-lock", "atomic-waker", "cfg-if", "futures-core", "futures-io", "rustix", "signal-hook-registry", "slab", "windows-sys 0.59.0", ] [[package]] name = "async-task" version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" version = "0.1.81" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "blocking" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ "async-channel", "async-task", "futures-io", "futures-lite", "piper", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" version = "1.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" dependencies = [ "shlex", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cfg_aliases" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "concurrent-queue" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] [[package]] name = "cpufeatures" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "endi" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" [[package]] name = "enumflags2" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" dependencies = [ "enumflags2_derive", "serde", ] [[package]] name = "enumflags2_derive" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "event-listener" version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ "event-listener", "pin-project-lite", ] [[package]] name = "fastrand" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "gumdrop" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" dependencies = [ "gumdrop_derive", ] [[package]] name = "gumdrop_derive" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "indexmap" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "io-lifetimes" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ "hermit-abi 0.3.9", "libc", "windows-sys 0.48.0", ] [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libudev-sys" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" dependencies = [ "libc", "pkg-config", ] [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logind-zbus" version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a85a07c35bc3d71bd5ce956b6e89420fbce7e221da18ce2c685c0c784e64fa01" dependencies = [ "serde", "zbus", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ "bitflags", "cfg-if", "cfg_aliases", "libc", "memoffset", ] [[package]] name = "object" version = "0.36.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "ordered-stream" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" dependencies = [ "futures-core", "pin-project-lite", ] [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", "fastrand", "futures-io", ] [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "polling" version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", "rustix", "tracing", "windows-sys 0.59.0", ] [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" version = "0.38.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.209" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "serde_json" version = "1.0.127" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_repr" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "supergfxctl" version = "5.2.4" dependencies = [ "env_logger", "gumdrop", "log", "logind-zbus", "serde", "serde_derive", "serde_json", "tokio", "udev", "zbus", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.59.0", ] [[package]] name = "tokio" version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", "tracing", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "udev" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ba005bcd5b1158ae3cd815905990e8b6ee4ba9ee7adbab6d7b58d389ad09c93" dependencies = [ "io-lifetimes", "libc", "libudev-sys", "pkg-config", ] [[package]] name = "uds_windows" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ "memoffset", "tempfile", "winapi", ] [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] [[package]] name = "xdg-home" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "zbus" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" dependencies = [ "async-broadcast", "async-executor", "async-fs", "async-io", "async-lock", "async-process", "async-recursion", "async-task", "async-trait", "blocking", "enumflags2", "event-listener", "futures-core", "futures-sink", "futures-util", "hex", "nix", "ordered-stream", "rand", "serde", "serde_repr", "sha1", "static_assertions", "tokio", "tracing", "uds_windows", "windows-sys 0.52.0", "xdg-home", "zbus_macros", "zbus_names", "zvariant", ] [[package]] name = "zbus_macros" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.76", "zvariant_utils", ] [[package]] name = "zbus_names" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", "zvariant", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] [[package]] name = "zvariant" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" dependencies = [ "endi", "enumflags2", "serde", "static_assertions", "zvariant_derive", ] [[package]] name = "zvariant_derive" version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.76", "zvariant_utils", ] [[package]] name = "zvariant_utils" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", "syn 2.0.76", ] 07070100000007000081A400000000000000000000000166FDDEC80000051C000000000000000000000000000000000000001D00000000supergfxctl-5.2.4/Cargo.toml[package] name = "supergfxctl" version = "5.2.4" license = "MPL-2.0" readme = "README.md" authors = ["Luke <luke@ljones.dev>"] repository = "https://gitlab.com/asus-linux/asusctl" homepage = "https://gitlab.com/asus-linux/asusctl" documentation = "https://docs.rs/rog-anime" description = "Types useful for fancy keyboards on ASUS ROG laptops" keywords = ["graphics", "nvidia", "switching"] edition = "2021" rust-version = "1.64" exclude = ["data"] [features] default = ["daemon", "cli", "zbus_tokio"] daemon = ["env_logger"] cli = ["gumdrop"] zbus_tokio = ["zbus/tokio"] [lib] name = "supergfxctl" path = "src/lib.rs" [[bin]] name = "supergfxd" path = "src/daemon.rs" required-features = ["daemon"] [[bin]] name = "supergfxctl" path = "src/cli.rs" required-features = ["cli"] [dependencies] udev = "~0.9.0" serde = "^1.0" serde_derive = "^1.0" serde_json = "^1.0" log = "^0.4" zbus = { version = "~4.4" } logind-zbus = { version = "~4.0.4" } tokio = { version = "^1.21.2", features = ["macros", "rt-multi-thread", "time"]} env_logger = { version = "~0.11.0", optional = true } gumdrop = { version = "^0.8", optional = true } [profile.release] lto = true strip = true debug = false opt-level = 3 panic = "abort" [profile.dev] debug = false opt-level = 1 [profile.bench] debug = false opt-level = 3 07070100000008000081A400000000000000000000000166FDDEC800004155000000000000000000000000000000000000001A00000000supergfxctl-5.2.4/LICENSEMozilla Public License Version 2.0 ================================== 1. Definitions -------------- 1.1. "Contributor" means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. 1.2. "Contributor Version" means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. 1.3. "Contribution" means Covered Software of a particular Contributor. 1.4. "Covered Software" means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. 1.5. "Incompatible With Secondary Licenses" means (a) that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or (b) that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. 1.6. "Executable Form" means any form of the work other than Source Code Form. 1.7. "Larger Work" means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. 1.8. "License" means this document. 1.9. "Licensable" means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. 1.10. "Modifications" means any of the following: (a) any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or (b) any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" means the form of the work preferred for making modifications. 1.14. "You" (or "Your") means an individual or a legal entity exercising rights under this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, "control" means (a) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (b) ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. 2. License Grants and Conditions -------------------------------- 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: (a) under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and (b) under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: (a) for any code that a Contributor has removed from Covered Software; or (b) for infringements caused by: (i) Your and any other third party's modifications of Covered Software, or (ii) the combination of its Contributions with other software (except as part of its Contributor Version); or (c) under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. 3. Responsibilities ------------------- 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: (a) such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and (b) You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. 4. Inability to Comply Due to Statute or Regulation --------------------------------------------------- If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: (a) comply with the terms of this License to the maximum extent possible; and (b) describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. 5. Termination -------------- 5.1. The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated (a) provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. 5.2. If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ************************************************************************ * * * 6. Disclaimer of Warranty * * ------------------------- * * * * Covered Software is provided under this License on an "as is" * * basis, without warranty of any kind, either expressed, implied, or * * statutory, including, without limitation, warranties that the * * Covered Software is free of defects, merchantable, fit for a * * particular purpose or non-infringing. The entire risk as to the * * quality and performance of the Covered Software is with You. * * Should any Covered Software prove defective in any respect, You * * (not any Contributor) assume the cost of any necessary servicing, * * repair, or correction. This disclaimer of warranty constitutes an * * essential part of this License. No use of any Covered Software is * * authorized under this License except under this disclaimer. * * * ************************************************************************ ************************************************************************ * * * 7. Limitation of Liability * * -------------------------- * * * * Under no circumstances and under no legal theory, whether tort * * (including negligence), contract, or otherwise, shall any * * Contributor, or anyone who distributes Covered Software as * * permitted above, be liable to You for any direct, indirect, * * special, incidental, or consequential damages of any character * * including, without limitation, damages for lost profits, loss of * * goodwill, work stoppage, computer failure or malfunction, or any * * and all other commercial damages or losses, even if such party * * shall have been informed of the possibility of such damages. This * * limitation of liability shall not apply to liability for death or * * personal injury resulting from such party's negligence to the * * extent applicable law prohibits such limitation. Some * * jurisdictions do not allow the exclusion or limitation of * * incidental or consequential damages, so this exclusion and * * limitation may not apply to You. * * * ************************************************************************ 8. Litigation ------------- Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. 9. Miscellaneous ---------------- This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. 10. Versions of the License --------------------------- 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. Exhibit A - Source Code Form License Notice ------------------------------------------- This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice --------------------------------------------------------- This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. 07070100000009000081A400000000000000000000000166FDDEC8000008ED000000000000000000000000000000000000001B00000000supergfxctl-5.2.4/MakefileVERSION := $(shell grep -Pm1 'version = "(\d.\d.\d)"' Cargo.toml | cut -d'"' -f2) INSTALL = install INSTALL_PROGRAM = ${INSTALL} -D -m 0755 INSTALL_DATA = ${INSTALL} -D -m 0644 prefix = /usr exec_prefix = $(prefix) bindir = $(exec_prefix)/bin datarootdir = $(prefix)/share libdir = $(exec_prefix)/lib BIN_SD := supergfxd BIN_SC := supergfxctl SERVICE := supergfxd.service PRESET := supergfxd.preset DBUSCFG := org.supergfxctl.Daemon.conf X11CFG := 90-nvidia-screen-G05.conf PMRULES := 90-supergfxd-nvidia-pm.rules SRC := Cargo.toml Cargo.lock Makefile $(shell find -type f -wholename '**/src/*.rs') DEBUG ?= 0 ifeq ($(DEBUG),0) ARGS += --release TARGET = release endif VENDORED ?= 0 ifeq ($(VENDORED),1) ARGS += --frozen endif all: build clean: cargo clean distclean: rm -rf .cargo vendor vendor-$(VERSION).tar.xz install: $(INSTALL_PROGRAM) "./target/release/$(BIN_SD)" "$(DESTDIR)$(bindir)/$(BIN_SD)" $(INSTALL_PROGRAM) "./target/release/$(BIN_SC)" "$(DESTDIR)$(bindir)/$(BIN_SC)" $(INSTALL_DATA) "./data/$(SERVICE)" "$(DESTDIR)$(libdir)/systemd/system/$(SERVICE)" $(INSTALL_DATA) "./data/$(PRESET)" "$(DESTDIR)$(libdir)/systemd/system-preset/$(PRESET)" $(INSTALL_DATA) "./data/$(DBUSCFG)" "$(DESTDIR)$(datarootdir)/dbus-1/system.d/$(DBUSCFG)" $(INSTALL_DATA) "./data/$(X11CFG)" "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)" $(INSTALL_DATA) "./data/$(PMRULES)" "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)" uninstall: rm -f "$(DESTDIR)$(bindir)/$(BIN_SC)" rm -f "$(DESTDIR)$(bindir)/$(BIN_SD)" rm -f "$(DESTDIR)$(libdir)/systemd/system/$(SERVICE)" rm -f "$(DESTDIR)$(libdir)/systemd/system-preset/$(PRESET)" rm -f "$(DESTDIR)$(datarootdir)/dbus-1/system.d/org.supergfxctl.Daemon.conf" rm -f "$(DESTDIR)$(datarootdir)/X11/xorg.conf.d/$(X11CFG)" rm -f "$(DESTDIR)$(libdir)/udev/rules.d/$(PMRULES)" update: cargo update vendor: mkdir -p .cargo cargo vendor-filterer --platform x86_64-unknown-linux-gnu vendor tar pcfJ vendor-$(VERSION).tar.xz vendor rm -rf vendor build: ifeq ($(VENDORED),1) @echo "version = $(VERSION)" tar pxf vendor-$(VERSION).tar.xz endif cargo build --features "daemon cli" $(ARGS) strip -s ./target/release/$(BIN_SD) strip -s ./target/release/$(BIN_SC) .PHONY: all clean distclean install uninstall update build 0707010000000A000081A400000000000000000000000166FDDEC80000168B000000000000000000000000000000000000001C00000000supergfxctl-5.2.4/README.md# supergfxctl **You need this only if you:** 1. have a laptop that can't suspend its dGPU 2. need an easy way to use vfio 3. want to monitor the dGPU status 4. want to try using hotplug and/or ASUS ROG dgpu_disable (results will vary) 5. have an ASUS with an eGPU and need to switch to it (testing required please) **Xorg is no-longer supported (but supergfxd still works with it)** `supergfxd` can switch graphics modes between: - `Hybrid`, enables dGPU-offload mode - `Integrated`, uses the iGPU only and force-disables the dGPU - `Vfio`, binds the dGPU to vfio for VM pass-through **If rebootless switch fails:** you may need the following: ``` sudo sed -i 's/#KillUserProcesses=no/KillUserProcesses=yes/' /etc/systemd/logind.conf ``` **ASUS ROG Flow series only** - `AsusEgpu`, this is for certain ASUS laptops like 13" Flow to enable external GPU. The option shows up automatically if detected. **Other ASUS gaming laptops** - `AsusMuxDgpu`, toggle the ASUS MUX to use dGPU as primary. The option shows up automatically if detected. (A reboot is *always* required due to how this works in ACPI) - `hotplug_type` config option, see end of this doc. This switcher conflicts with other gpu switchers like optimus-manager, suse-prime or ubuntu-prime, system76-power, and bbswitch. If you have issues with `supergfxd` always defaulting to `integrated` mode on boot then you will need to check for stray configs blocking nvidia modules from loading in: - `/etc/modprobe.d/` - `/usr/lib/modprope.d/` ASUS laptops require a kernel 6.1.x or newer. **Runtime D3** Most Nvidia laptops support different levels of runtime power management that can prolong battery life. These have been buggy in some use cases, so they are not set by default. To configure these, copy this text into a file other than `supergfxd.conf` in `/etc/modprobe.d/` and modify it to meet your needs. ``` # 0x00 - no power management, 0x01 - coarse power management, 0x02 - fine power management options nvidia NVreg_DynamicPowerManagement=0x02 ``` More information is available [here](https://download.nvidia.com/XFree86/Linux-x86_64/530.41.03/README/dynamicpowermanagement.html). ## Building First you need to install the dev packages required. * Debian/Ubuntu: `sudo apt update && sudo apt install curl git build-essential` * Fedora/RHEL: `sudo dnf upgrade && sudo dnf install curl git && sudo dnf groupinstall "Development Tools"` * Arch/Manjaro: `sudo pacman -Syu && sudo pacman -S curl git base-devel` **Install Rust** ``` curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh source ~/.cargo/env ``` **Clone supergfxctl repository** `git clone https://gitlab.com/asus-linux/supergfxctl.git` **Install supergfxctl** ``` cd supergfxctl make && sudo make install ``` **Enable and start the service** `sudo systemctl enable supergfxd.service --now` **Add user to group** Depending on your distro add your username to one of `adm`, `users`, `wheel` then refresh your session (easiest way is to reboot). * `sudo usermod -a -G users $USER` **Switch GPU modes** * Switching to/from Hybrid mode requires a logout only. (no reboot) * Switching between integrated/vfio is instant. (no logout or reboot) * Mode can be set via kernel cmdline with `supergfxd.mode=`. Capitalisation does not matter. | GPU Modes | Command | |------------|-------------------------------| | Integrated | supergfxctl --mode Integrated | | Hybrid | supergfxctl --mode Hybrid | | VFIO | supergfxctl --mode Vfio | | AsusEgpu | supergfxctl --mode AsusEgpu | | AsusMuxDgpu| supergfxctl --mode AsusMuxDgpu| #### supergfxctl ``` supergfxctl --help Optional arguments: -h, --help print help message -m, --mode Set graphics mode -v, --version Get supergfxd version -g, --get Get the current mode -s, --supported Get the supported modes -V, --vendor Get the dGPU vendor name -S, --status Get the current power status -p, --pend-action Get the pending user action if any -P, --pend-mode Get the pending mode change if any ``` #### Config options /etc/supergfxd.conf 1. `mode`: <MODE> : any of supported modes, must be capitalised 2. `vfio_enable` <bool> : enable vfio switching for dGPU passthrough 3. `vfio_save` <bool> : save vfio state in mode (so it sticks between boots) 5. `always_reboot` <bool> : always require a reboot to change modes (helps some laptops) 6. `no_logind` <bool> : don't use logind to see if all sessions are logged out and therefore safe to change mode. This will be useful for people not using a login manager. Ignored if `always_reboot` is set. 7. `logout_timeout_s` <u64> : the timeout in seconds to wait for all user graphical sessions to end. Default is 3 minutes, 0 = infinite. Ignored if `no_logind` or `always_reboot` is set. 8. `hotplug_type` <enum> : None (default), Std, or Asus. Std tries to use the kernel hotplug mechanism if available, while Asus tries to use dgpu_disable if available **You must restart the service if you edit the config file** **Changing hotplug_type requires a reboot to ensure correct state**, for example if you were in integrated mode with `hotplug_type = Asus` and changed to `hotplug_type = None` you would not have dGPU available until reboot. #### Graphics switching notes **ASUS G-Sync + ASUS GPU-MUX note:** This can also be set by asusctl. If you don't require anything but Hybrid mode usually, then asusctl may be the better option for you if you also want the ability to toggle the MUX sometimes. **vfio note:** The vfio modules *must not* be compiled into the kernel, they need to be separate modules. If you don't plan to use vfio mode then you can ignore this otherwise you may need a custom built kernel. 0707010000000B000041ED00000000000000000000000266FDDEC800000000000000000000000000000000000000000000001700000000supergfxctl-5.2.4/data0707010000000C000081A400000000000000000000000166FDDEC800000059000000000000000000000000000000000000003100000000supergfxctl-5.2.4/data/90-nvidia-screen-G05.confSection "ServerLayout" Identifier "layout" Option "AllowNVIDIAGPUScreens" EndSection 0707010000000D000081A400000000000000000000000166FDDEC8000002A8000000000000000000000000000000000000003400000000supergfxctl-5.2.4/data/90-supergfxd-nvidia-pm.rules# Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto" ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto" # Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on" ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on" 0707010000000E000081A400000000000000000000000166FDDEC80000012D000000000000000000000000000000000000002A00000000supergfxctl-5.2.4/data/99-nvidia-ac.rules# Stop nvidia_powerd on battery SUBSYSTEM=="power_supply",ENV{POWER_SUPPLY_ONLINE}=="0",RUN+="/usr/bin/systemctl --no-block stop nvidia-powerd.service" # Start nvidia_powerd on AC SUBSYSTEM=="power_supply",ENV{POWER_SUPPLY_ONLINE}=="1",RUN+="/usr/bin/systemctl --no-block start nvidia-powerd.service" 0707010000000F000081A400000000000000000000000166FDDEC8000003F7000000000000000000000000000000000000003300000000supergfxctl-5.2.4/data/org.supergfxctl.Daemon.conf<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy group="adm"> <allow send_destination="org.supergfxctl.Daemon"/> <allow receive_sender="org.supergfxctl.Daemon"/> </policy> <policy group="sudo"> <allow send_destination="org.supergfxctl.Daemon"/> <allow receive_sender="org.supergfxctl.Daemon"/> </policy> <policy group="users"> <allow send_destination="org.supergfxctl.Daemon"/> <allow receive_sender="org.supergfxctl.Daemon"/> </policy> <policy group="wheel"> <allow send_destination="org.supergfxctl.Daemon"/> <allow receive_sender="org.supergfxctl.Daemon"/> </policy> <policy user="root"> <allow own="org.supergfxctl.Daemon"/> <allow send_destination="org.supergfxctl.Daemon"/> <allow receive_sender="org.supergfxctl.Daemon"/> </policy> </busconfig> 07070100000010000081A400000000000000000000000166FDDEC800000019000000000000000000000000000000000000002800000000supergfxctl-5.2.4/data/supergfxd.presetenable supergfxd.service 07070100000011000081A400000000000000000000000166FDDEC8000001EE000000000000000000000000000000000000002900000000supergfxctl-5.2.4/data/supergfxd.service[Unit] Description=SUPERGFX StartLimitInterval=200 StartLimitBurst=2 Before=graphical.target Before=multi-user.target Before=display-manager.service Before=nvidia-powerd.service [Service] Environment=IS_SERVICE=1 Environment=RUST_LOG=debug ExecStart=/usr/bin/supergfxd Restart=on-failure Restart=always RestartSec=1 Type=dbus BusName=org.supergfxctl.Daemon SELinuxContext=system_u:system_r:unconfined_t:s0 #SELinuxContext=system_u:object_r:modules_object_t:s0 [Install] WantedBy=getty.target 07070100000012000041ED00000000000000000000000266FDDEC800000000000000000000000000000000000000000000001600000000supergfxctl-5.2.4/src07070100000013000081A400000000000000000000000166FDDEC800006CE4000000000000000000000000000000000000002100000000supergfxctl-5.2.4/src/actions.rsuse std::{ fmt::Display, sync::{ atomic::{AtomicBool, Ordering}, Arc, }, time::{Duration, Instant}, }; use log::{debug, info, warn}; use logind_zbus::{ manager::{ManagerProxy, SessionInfo}, session::{SessionClass, SessionProxy, SessionState, SessionType}, }; use serde::{Deserialize, Serialize}; use tokio::time::sleep; use zbus::zvariant::Type; use zbus::Connection; use crate::{ config::{check_vulkan_icd, create_modprobe_conf, GfxConfig}, do_driver_action, error::GfxError, kill_nvidia_lsof, pci_device::{rescan_pci_bus, DiscreetGpu, GfxMode, GfxVendor, HotplugState, HotplugType}, special_asus::{asus_dgpu_set_disabled, asus_egpu_set_enabled, asus_gpu_mux_set_igpu}, systemd::{ do_systemd_unit_action, wait_systemd_unit_state, SystemdUnitAction, SystemdUnitState, }, toggle_nvidia_powerd, DriverAction, DISPLAY_MANAGER, VFIO_DRIVERS, }; pub enum Action { UserAction(UserActionRequired), StagedActions(Vec<StagedAction>), } #[derive(Debug, Clone, Copy, Serialize, Deserialize, Type)] /// The action required by the user after they request a supergfx action pub enum UserActionRequired { Logout, Reboot, SwitchToIntegrated, AsusEgpuDisable, Nothing, } impl UserActionRequired { /// Determine if we need to logout/thread. Integrated<->Vfio mode does not /// require logout. pub fn mode_change_action(new_mode: GfxMode, current_mode: GfxMode) -> Self { match new_mode { GfxMode::Hybrid => match current_mode { GfxMode::Integrated | GfxMode::AsusEgpu => Self::Logout, GfxMode::AsusMuxDgpu => Self::Reboot, GfxMode::Vfio => Self::SwitchToIntegrated, GfxMode::NvidiaNoModeset | GfxMode::Hybrid | GfxMode::None => Self::Nothing, }, GfxMode::Integrated => match current_mode { GfxMode::Hybrid | GfxMode::AsusEgpu => Self::Logout, GfxMode::AsusMuxDgpu => Self::Reboot, GfxMode::Vfio | GfxMode::NvidiaNoModeset | GfxMode::Integrated | GfxMode::None => { Self::Nothing } }, GfxMode::NvidiaNoModeset => match current_mode { GfxMode::Integrated | GfxMode::NvidiaNoModeset | GfxMode::Vfio | GfxMode::Hybrid | GfxMode::None => Self::Nothing, GfxMode::AsusEgpu => Self::Logout, GfxMode::AsusMuxDgpu => Self::Reboot, }, GfxMode::Vfio => match current_mode { GfxMode::Integrated | GfxMode::Vfio | GfxMode::NvidiaNoModeset | GfxMode::None => { Self::Nothing } GfxMode::AsusEgpu | GfxMode::Hybrid => Self::Logout, GfxMode::AsusMuxDgpu => Self::Reboot, }, GfxMode::AsusEgpu => match current_mode { GfxMode::Integrated | GfxMode::Hybrid | GfxMode::NvidiaNoModeset => Self::Logout, GfxMode::Vfio => Self::SwitchToIntegrated, GfxMode::AsusEgpu | GfxMode::None => Self::Nothing, GfxMode::AsusMuxDgpu => Self::Reboot, }, GfxMode::AsusMuxDgpu => match current_mode { GfxMode::Hybrid | GfxMode::Integrated | GfxMode::NvidiaNoModeset | GfxMode::Vfio | GfxMode::AsusEgpu => Self::Reboot, GfxMode::None | GfxMode::AsusMuxDgpu => Self::Nothing, }, GfxMode::None => Self::Nothing, } } } impl Display for UserActionRequired { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Logout => write!(f, "Logout"), Self::Reboot => write!(f, "Reboot"), Self::SwitchToIntegrated => write!(f, "SwitchToIntegrated"), Self::AsusEgpuDisable => write!(f, "AsusEgpuDisable"), Self::Nothing => write!(f, "Nothing"), } } } impl From<UserActionRequired> for &str { /// Convert the action to a verbose string fn from(gfx: UserActionRequired) -> &'static str { match gfx { UserActionRequired::Logout => "Logout required to complete mode change", UserActionRequired::Reboot => "Reboot required to complete mode change", UserActionRequired::SwitchToIntegrated => "You must switch to Integrated first", UserActionRequired::Nothing => "No action required", UserActionRequired::AsusEgpuDisable => { "The mode must be switched to Integrated or Hybrid first" } } } } impl From<&UserActionRequired> for &str { fn from(gfx: &UserActionRequired) -> &'static str { (*gfx).into() } } /// All the possible actions supergfx can perform. These should be chucked in /// a vector in the order required to perform them. #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] pub enum StagedAction { /// Wait for the user to logout WaitLogout, /// Stop the display manager StopDisplayManager, /// Restart the display manager StartDisplayManager, /// A marker for no logind options NoLogind, /// Load the dgpu drivers LoadGpuDrivers, /// Unload the dgpu drivers UnloadGpuDrivers, /// Kill all things using the nvidia device KillNvidia, /// Kill all things using the AMD device KillAmd, /// Enable nvidia-powerd service EnableNvidiaPowerd, /// Disable nvidia-powerd service DisableNvidiaPowerd, /// Load the vfio modules LoadVfioDrivers, /// Unload the vfio modules UnloadVfioDrivers, /// A none-action marker to specify an intent, in this case not using ASUS or hotplug device removal and only dev-tree unbind/remove DevTreeManaged, RescanPci, /// Unbind and fully remove the device from a driver using sysfs UnbindRemoveGpu, /// Unbind only, device is still in PCI tree UnbindGpu, /// If hotplug is available then the dgpu can be hot-removed HotplugUnplug, /// If hotplug is available then the dgpu can be hot-plugged HotplugPlug, /// Disable the internal dgpu using the ASUS ACPI method. This does a hard removal of the device and a pci-scan will no-longer find it AsusDgpuDisable, /// Enable the internal dgpu using the ASUS ACPI method. This must be done for it to be seen on the pci bus again after an `AsusDgpuDisable` AsusDgpuEnable, /// This will also disable the internal dgpu due to the laptop ACPI functions being called AsusEgpuDisable, /// This will also enable the internal dgpu due to the laptop ACPI functions being called AsusEgpuEnable, /// Switch the ASUS MUX to igpu mode AsusMuxIgpu, /// Switch the ASUS MUX to dgpu mode AsusMuxDgpu, /// Write a modprobe conf according to mode (e.g, hybrid, vfio) WriteModprobeConf, /// Checks for correct Vulkan ICD (remove nvidia_icd.json if not on "nvidia" or "vfio") CheckVulkanIcd, /// Placeholder, used to indicate the dgpu is not Nvidia (for example when deciding if KillNvidia should be used) NotNvidia, None, } impl StagedAction { /// Generate a series of initial mode steps, these are specific to booting the system only, not changing modes pub fn action_list_for_boot( config: &GfxConfig, vendor: GfxVendor, mode: GfxMode, ) -> Vec<StagedAction> { let kill_gpu_use = if vendor == GfxVendor::Nvidia { Self::KillNvidia } else { Self::KillAmd }; let disable_nvidia_powerd = if vendor == GfxVendor::Nvidia { Self::DisableNvidiaPowerd } else { Self::NotNvidia }; let enable_nvidia_powerd = if vendor == GfxVendor::Nvidia { Self::EnableNvidiaPowerd } else { Self::NotNvidia }; let hotplug_rm_type = match config.hotplug_type { HotplugType::Std => Self::HotplugUnplug, HotplugType::Asus => Self::AsusDgpuDisable, HotplugType::None => Self::DevTreeManaged, }; let hotplug_add_type = match config.hotplug_type { HotplugType::Std => Self::HotplugPlug, HotplugType::Asus => Self::AsusDgpuEnable, HotplugType::None => Self::DevTreeManaged, }; match mode { GfxMode::Hybrid => vec![ Self::WriteModprobeConf, Self::CheckVulkanIcd, hotplug_add_type, Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, ], GfxMode::Integrated | GfxMode::NvidiaNoModeset => vec![ disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::CheckVulkanIcd, hotplug_rm_type, ], GfxMode::Vfio => vec![ disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::LoadVfioDrivers, ], GfxMode::AsusEgpu => vec![ Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::LoadGpuDrivers, enable_nvidia_powerd, ], GfxMode::AsusMuxDgpu => vec![ // TODO: remove iGPU Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::LoadGpuDrivers, enable_nvidia_powerd, ], GfxMode::None => vec![], } } /// Generate a well defined list of specific actions required for the mode switch. // // There might be some redundancy in this list but it is preferred so as to force checking of all conditions for from/to combos pub fn action_list_for_switch( config: &GfxConfig, vendor: GfxVendor, from: GfxMode, to: GfxMode, ) -> Action { let mut wait_logout = Self::NoLogind; let mut stop_display = Self::NoLogind; let mut start_display = Self::NoLogind; if !config.no_logind & !config.always_reboot { wait_logout = Self::WaitLogout; stop_display = Self::StopDisplayManager; start_display = Self::StartDisplayManager; }; let mut kill_gpu_use = Self::NotNvidia; // the nvida powerd toggle function only runs if the vendor is nvidia let disable_nvidia_powerd = Self::DisableNvidiaPowerd; let enable_nvidia_powerd = Self::EnableNvidiaPowerd; if vendor == GfxVendor::Nvidia { kill_gpu_use = Self::KillNvidia; // disable_nvidia_powerd = Self::DisableNvidiaPowerd; // enable_nvidia_powerd = Self::EnableNvidiaPowerd; } else if vendor == GfxVendor::Amd { kill_gpu_use = Self::KillAmd; } let hotplug_rm_type = match config.hotplug_type { HotplugType::Std => Self::HotplugUnplug, HotplugType::Asus => Self::AsusDgpuDisable, HotplugType::None => Self::DevTreeManaged, }; let hotplug_add_type = match config.hotplug_type { HotplugType::Std => Self::HotplugPlug, HotplugType::Asus => Self::AsusDgpuEnable, HotplugType::None => Self::DevTreeManaged, }; // Be verbose in this list of actions. It's okay to have repeated blocks as this makes it much clearer // which action chain results from which switching combo match from { GfxMode::Hybrid => match to { GfxMode::Integrated => Action::StagedActions(vec![ wait_logout, stop_display, disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::CheckVulkanIcd, hotplug_rm_type, start_display, ]), // Ask the user to do the switch instead of doing something unexpected GfxMode::Vfio => Action::UserAction(UserActionRequired::SwitchToIntegrated), GfxMode::AsusEgpu => Action::StagedActions(vec![ wait_logout, stop_display, disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::AsusEgpuEnable, Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, start_display, ]), GfxMode::AsusMuxDgpu => Action::StagedActions(vec![ // Self::WriteModprobeConf, Self::CheckVulkanIcd, // check this in anycase enable_nvidia_powerd, Self::AsusMuxDgpu, ]), GfxMode::Hybrid | GfxMode::NvidiaNoModeset | GfxMode::None => { Action::UserAction(UserActionRequired::Nothing) } }, GfxMode::Integrated => match to { GfxMode::Hybrid => Action::StagedActions(vec![ wait_logout, stop_display, Self::WriteModprobeConf, Self::CheckVulkanIcd, hotplug_add_type, Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, start_display, ]), GfxMode::NvidiaNoModeset => Action::StagedActions(vec![ Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, ]), GfxMode::Vfio => Action::StagedActions(vec![ Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::RescanPci, // Make the PCI devices available disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, // rescan can load the gpu drivers automatically Self::UnbindGpu, Self::LoadVfioDrivers, ]), GfxMode::AsusEgpu => Action::StagedActions(vec![ wait_logout, stop_display, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::AsusEgpuEnable, Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, start_display, ]), GfxMode::AsusMuxDgpu => Action::StagedActions(vec![ Self::WriteModprobeConf, Self::CheckVulkanIcd, hotplug_add_type, // must always assume the possibility dgpu_disable was set enable_nvidia_powerd, Self::AsusMuxDgpu, ]), GfxMode::Integrated | GfxMode::None => { Action::UserAction(UserActionRequired::Nothing) } }, GfxMode::NvidiaNoModeset => match to { GfxMode::Hybrid => Action::UserAction(UserActionRequired::Nothing), GfxMode::Integrated => Action::StagedActions(vec![ disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::CheckVulkanIcd, ]), GfxMode::Vfio => Action::StagedActions(vec![ disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::LoadVfioDrivers, ]), GfxMode::AsusEgpu => Action::UserAction(UserActionRequired::Nothing), GfxMode::AsusMuxDgpu => Action::StagedActions(vec![ // Self::WriteModprobeConf, enable_nvidia_powerd, Self::AsusMuxDgpu, ]), GfxMode::NvidiaNoModeset | GfxMode::None => { Action::UserAction(UserActionRequired::Nothing) } }, GfxMode::Vfio => match to { GfxMode::Hybrid | GfxMode::NvidiaNoModeset => Action::StagedActions(vec![ kill_gpu_use, Self::UnloadVfioDrivers, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::RescanPci, Self::LoadGpuDrivers, ]), GfxMode::Integrated => Action::StagedActions(vec![ kill_gpu_use, Self::UnloadVfioDrivers, Self::UnbindRemoveGpu, ]), GfxMode::AsusEgpu => Action::StagedActions(vec![ wait_logout, stop_display, Self::UnloadVfioDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::AsusEgpuEnable, Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, start_display, ]), GfxMode::AsusMuxDgpu => Action::StagedActions(vec![ // Self::WriteModprobeConf, enable_nvidia_powerd, Self::AsusMuxDgpu, ]), GfxMode::Vfio | GfxMode::None => Action::UserAction(UserActionRequired::Nothing), }, GfxMode::AsusEgpu => match to { GfxMode::Hybrid => Action::StagedActions(vec![ wait_logout, stop_display, disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::CheckVulkanIcd, Self::AsusEgpuDisable, Self::AsusDgpuEnable, // ensure the dgpu is enabled Self::RescanPci, Self::LoadGpuDrivers, enable_nvidia_powerd, start_display, ]), GfxMode::Integrated => Action::StagedActions(vec![ wait_logout, stop_display, disable_nvidia_powerd, kill_gpu_use, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, Self::WriteModprobeConf, Self::AsusEgpuDisable, Self::UnloadGpuDrivers, Self::UnbindRemoveGpu, // egpu disable also enable dgpu, which can reload the drivers Self::WriteModprobeConf, // TODO: called twice? (why?) Self::CheckVulkanIcd, hotplug_rm_type, // also need to ensure dgpu is off start_display, ]), GfxMode::Vfio => Action::UserAction(UserActionRequired::SwitchToIntegrated), GfxMode::AsusMuxDgpu => Action::UserAction(UserActionRequired::AsusEgpuDisable), GfxMode::AsusEgpu | GfxMode::NvidiaNoModeset | GfxMode::None => { Action::UserAction(UserActionRequired::Nothing) } }, // The mux change *ALWAYS* requires a reboot, so only switch to/from mux and hybrid GfxMode::AsusMuxDgpu => match to { GfxMode::AsusMuxDgpu => Action::UserAction(UserActionRequired::Nothing), _ => Action::StagedActions(vec![Self::AsusMuxIgpu]), }, GfxMode::None => Action::UserAction(UserActionRequired::Nothing), } } /// Do the work required by the action pub async fn perform( &self, changing_to: GfxMode, device: &mut DiscreetGpu, loop_exit: Arc<AtomicBool>, ) -> Result<(), GfxError> { match self { StagedAction::WaitLogout => wait_logout(loop_exit).await, StagedAction::StopDisplayManager => { do_systemd_unit_action(SystemdUnitAction::Stop, DISPLAY_MANAGER)?; wait_systemd_unit_state(SystemdUnitState::Inactive, DISPLAY_MANAGER) } StagedAction::StartDisplayManager => { do_systemd_unit_action(SystemdUnitAction::Start, DISPLAY_MANAGER) } StagedAction::LoadGpuDrivers => device.do_driver_action(DriverAction::Load), StagedAction::UnloadGpuDrivers => device.do_driver_action(DriverAction::Remove), StagedAction::LoadVfioDrivers => do_driver_action("vfio-pci", DriverAction::Load), StagedAction::UnloadVfioDrivers => { for driver in VFIO_DRIVERS.iter() { do_driver_action(driver, DriverAction::Remove)?; } Ok(()) } StagedAction::KillNvidia => kill_nvidia_lsof(), StagedAction::KillAmd => { // TODO: do this Ok(()) } StagedAction::EnableNvidiaPowerd => toggle_nvidia_powerd(true, device.vendor()), StagedAction::DisableNvidiaPowerd => toggle_nvidia_powerd(false, device.vendor()), StagedAction::RescanPci => rescan_pci(device), StagedAction::UnbindRemoveGpu => device.unbind_remove(), StagedAction::UnbindGpu => device.unbind(), StagedAction::HotplugUnplug => device.set_hotplug(HotplugState::Off), StagedAction::HotplugPlug => device.set_hotplug(HotplugState::On), StagedAction::AsusDgpuDisable => asus_dgpu_set_disabled(true), StagedAction::AsusDgpuEnable => asus_dgpu_set_disabled(false), StagedAction::AsusEgpuDisable => asus_egpu_set_enabled(false), StagedAction::AsusEgpuEnable => asus_egpu_set_enabled(true), StagedAction::AsusMuxIgpu => asus_gpu_mux_set_igpu(true), StagedAction::AsusMuxDgpu => asus_gpu_mux_set_igpu(false), StagedAction::WriteModprobeConf => create_modprobe_conf(changing_to, device), StagedAction::CheckVulkanIcd => { check_vulkan_icd(changing_to) .map_err(|e| warn!("Vulkan ICD failed: {e:?}")) .ok(); Ok(()) } StagedAction::DevTreeManaged => Ok(()), StagedAction::NoLogind => Ok(()), StagedAction::NotNvidia => Ok(()), StagedAction::None => Ok(()), } } } /// Check if the user has any graphical uiser sessions that are active or online async fn graphical_user_sessions_exist( connection: &Connection, sessions: &[SessionInfo], ) -> Result<bool, GfxError> { for session in sessions { // should ignore error such as: // Zbus error: org.freedesktop.DBus.Error.UnknownObject: Unknown object '/org/freedesktop/login1/session/c2' if let Ok(session_proxy) = SessionProxy::builder(connection) .path(session.path())? .build() .await .map_err(|e| warn!("graphical_user_sessions_exist: builder: {e:?}")) { if let Ok(class) = session_proxy.class().await.map_err(|e| { warn!("graphical_user_sessions_exist: class: {e:?}"); e }) { if class == SessionClass::User { if let Ok(type_) = session_proxy.type_().await.map_err(|e| { warn!("graphical_user_sessions_exist: type_: {e:?}"); e }) { match type_ { SessionType::X11 | SessionType::Wayland | SessionType::MIR => { if let Ok(state) = session_proxy.state().await.map_err(|e| { warn!("graphical_user_sessions_exist: state: {e:?}"); e }) { match state { SessionState::Online | SessionState::Active => { return Ok(true) } SessionState::Closing => {} } } } _ => {} } } } } } } Ok(false) } /// It's async because of inner calls, but is a blocking loop // TODO: make it a Future async fn wait_logout(loop_exit: Arc<AtomicBool>) -> Result<(), GfxError> { loop_exit.store(false, Ordering::Release); const SLEEP_PERIOD: Duration = Duration::from_millis(100); let logout_timeout_s = 30; let start_time = Instant::now(); let connection = Connection::system().await?; let manager = ManagerProxy::new(&connection).await?; while !loop_exit.load(Ordering::Acquire) { let sessions = manager.list_sessions().await?; if !graphical_user_sessions_exist(&connection, &sessions).await? { break; } // exit if 3 minutes pass if logout_timeout_s != 0 && Instant::now().duration_since(start_time).as_secs() > logout_timeout_s { let detail = format!("Time ({} seconds) for logout exceeded", logout_timeout_s); warn!("mode_change_loop: {}", detail); return Err(GfxError::SystemdUnitWaitTimeout(detail)); } // Don't spin at max speed sleep(SLEEP_PERIOD).await; } loop_exit.store(false, Ordering::Release); debug!("wait_logout: loop exited"); Ok(()) } fn rescan_pci(device: &mut DiscreetGpu) -> Result<(), GfxError> { // Don't do a rescan unless the dev list is empty. This might be the case if // asus dgpu_disable is set before the daemon starts. But in general the daemon // should have the correct device on boot and retain that. let mut do_find_device = device.devices().is_empty(); for dev in device.devices() { if dev.is_dgpu() { do_find_device = false; break; } do_find_device = true; } if do_find_device { info!("do_rescan: Device rescan required"); match DiscreetGpu::new() { Ok(dev) => *device = dev, Err(e) => warn!("do_rescan: tried to reset Unknown dgpu status/devices: {e:?}"), } } else { info!("do_rescan: Rescanning PCI bus"); rescan_pci_bus()?; // should force re-attach of driver } Ok(()) } 07070100000014000081A400000000000000000000000166FDDEC800001387000000000000000000000000000000000000001D00000000supergfxctl-5.2.4/src/cli.rs//! Basic CLI tool to control the `supergfxd` daemon use std::{env::args, process::Command}; use supergfxctl::{ actions::UserActionRequired, error::GfxError, pci_device::GfxMode, zbus_proxy::DaemonProxyBlocking, }; use gumdrop::Options; use zbus::{blocking::Connection, proxy::CacheProperties}; #[derive(Default, Clone, Copy, Options)] struct CliStart { #[options(help = "print help message")] help: bool, #[options(meta = "", help = "Set graphics mode")] mode: Option<GfxMode>, #[options(help = "Get supergfxd version")] version: bool, #[options(help = "Get the current mode")] get: bool, #[options(help = "Get the supported modes")] supported: bool, #[options(help = "Get the dGPU vendor name")] vendor: bool, #[options(help = "Get the current power status")] status: bool, #[options(help = "Get the pending user action if any")] pend_action: bool, #[options(help = "Get the pending mode change if any")] pend_mode: bool, } fn main() -> Result<(), Box<dyn std::error::Error>> { let args: Vec<String> = args().skip(1).collect(); match CliStart::parse_args_default(&args) { Ok(command) => { do_gfx(command).map_err(|err|{ eprintln!("Graphics mode change error."); if !check_systemd_unit_enabled("supergfxd") { eprintln!("\x1b[0;31msupergfxd is not enabled, enable it with `systemctl enable supergfxd\x1b[0m"); } else if !check_systemd_unit_active("supergfxd") { eprintln!("\x1b[0;31msupergfxd is not running, start it with `systemctl start supergfxd\x1b[0m"); } else { eprintln!("Please check `journalctl -b -u supergfxd`, and `systemctl status supergfxd`"); if let GfxError::Zbus(zbus::Error::MethodError(_,Some(text),_)) = &err { eprintln!("\x1b[0;31m{}\x1b[0m", text); std::process::exit(1); } } eprintln!("Error: {}", err); std::process::exit(1); }).ok(); } Err(err) => { eprintln!("Error: {}", err); std::process::exit(1); } } Ok(()) } fn do_gfx(command: CliStart) -> Result<(), GfxError> { if command.mode.is_none() && !command.get && !command.version && !command.supported && !command.vendor && !command.status && !command.pend_action && !command.pend_mode || command.help { println!("{}", command.self_usage()); } let proxy = DaemonProxyBlocking::builder(&Connection::system()?) .cache_properties(CacheProperties::No) .build()?; if let Some(mode) = command.mode { let res = proxy.set_mode(&mode)?; match res { UserActionRequired::SwitchToIntegrated => { eprintln!("You must change to Integrated before you can change to {mode}",); std::process::exit(1); } UserActionRequired::Logout => { println!( "Graphics mode changed to {mode}. Required user action is: {}", <&str>::from(res) ); } UserActionRequired::Nothing => { println!("Graphics mode changed to {mode}"); } UserActionRequired::Reboot => { println!("A reboot is required to complete the mode change") } UserActionRequired::AsusEgpuDisable => println!("{res:?}"), } } if command.version { let res = proxy.version()?; println!("{}", res); } if command.get { let res = proxy.mode()?; println!("{res}"); } if command.supported { let res = proxy.supported()?; println!("{:?}", res); } if command.vendor { let res = proxy.vendor()?; println!("{}", res); } if command.status { let res = proxy.power()?; println!("{}", <&str>::from(&res)); } if command.pend_action { let res = proxy.pending_user_action()?; println!("{}", <&str>::from(&res)); } if command.pend_mode { let res = proxy.pending_mode()?; println!("{res}"); } Ok(()) } fn check_systemd_unit_active(name: &str) -> bool { if let Ok(out) = Command::new("systemctl") .arg("is-active") .arg(name) .output() { let buf = String::from_utf8_lossy(&out.stdout); return !buf.contains("inactive") && !buf.contains("failed"); } false } fn check_systemd_unit_enabled(name: &str) -> bool { if let Ok(out) = Command::new("systemctl") .arg("is-enabled") .arg(name) .output() { let buf = String::from_utf8_lossy(&out.stdout); return buf.contains("enabled"); } false } 07070100000015000081A400000000000000000000000166FDDEC8000020C5000000000000000000000000000000000000002000000000supergfxctl-5.2.4/src/config.rsuse log::{error, info, warn}; use serde_derive::{Deserialize, Serialize}; use std::fs::{File, OpenOptions}; use std::io::{Read, Write}; use zbus::zvariant::Type; use crate::actions::UserActionRequired; use crate::config_old::{GfxConfig300, GfxConfig405, GfxConfig500}; use crate::error::GfxError; use crate::pci_device::{DiscreetGpu, GfxMode, HotplugType}; use crate::{ CONFIG_NVIDIA_VKICD, MODPROBE_INTEGRATED, MODPROBE_NVIDIA_BASE, MODPROBE_NVIDIA_DRM_MODESET_ON, MODPROBE_PATH, MODPROBE_VFIO, }; /// Cleaned config for passing over dbus only #[derive(Debug, Clone, Deserialize, Serialize, Type)] pub struct GfxConfigDbus { pub mode: GfxMode, pub vfio_enable: bool, pub vfio_save: bool, pub always_reboot: bool, pub no_logind: bool, pub logout_timeout_s: u64, pub hotplug_type: HotplugType, } impl From<&GfxConfig> for GfxConfigDbus { fn from(c: &GfxConfig) -> Self { Self { mode: c.mode, vfio_enable: c.vfio_enable, vfio_save: c.vfio_save, always_reboot: c.always_reboot, no_logind: c.no_logind, logout_timeout_s: c.logout_timeout_s, hotplug_type: c.hotplug_type, } } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GfxConfig { #[serde(skip)] pub config_path: String, /// The current mode set, also applies on boot pub mode: GfxMode, /// Only for temporary modes like compute or vfio #[serde(skip)] pub tmp_mode: Option<GfxMode>, /// Just for tracking the requested mode change in rebootless mode #[serde(skip)] pub pending_mode: Option<GfxMode>, /// Just for tracking the required user action #[serde(skip)] pub pending_action: Option<UserActionRequired>, /// Set if vfio option is enabled. This requires the vfio drivers to be built as modules pub vfio_enable: bool, /// Save the VFIO mode so that it is reloaded on boot pub vfio_save: bool, /// Should always reboot? pub always_reboot: bool, /// Don't use logind to see if all sessions are logged out and therefore safe to change mode pub no_logind: bool, /// The timeout in seconds to wait for all user graphical sessions to end. Default is 3 minutes, 0 = infinite. Ignored if `no_logind` or `always_reboot` is set. pub logout_timeout_s: u64, /// The type of method to use for hotplug. ASUS is... fiddly. pub hotplug_type: HotplugType, } impl GfxConfig { fn new(config_path: String) -> Self { Self { config_path, mode: GfxMode::Hybrid, tmp_mode: None, pending_mode: None, pending_action: None, vfio_enable: false, vfio_save: false, always_reboot: false, no_logind: false, logout_timeout_s: 180, hotplug_type: HotplugType::None, } } /// `load` will attempt to read the config, and panic if the dir is missing pub fn load(config_path: String) -> Self { let mut file = OpenOptions::new() .read(true) .write(true) .create(true) .open(&config_path) .unwrap_or_else(|_| panic!("The directory {} is missing", config_path)); // okay to cause panic here let mut buf = String::new(); let mut config; if let Ok(read_len) = file.read_to_string(&mut buf) { if read_len == 0 { config = Self::new(config_path); } else if let Ok(data) = serde_json::from_str(&buf) { config = data; config.config_path = config_path; } else if let Ok(data) = serde_json::from_str(&buf) { let old: GfxConfig300 = data; config = old.into(); config.config_path = config_path; } else if let Ok(data) = serde_json::from_str(&buf) { let old: GfxConfig405 = data; config = old.into(); config.config_path = config_path; } else if let Ok(data) = serde_json::from_str(&buf) { let old: GfxConfig500 = data; config = old.into(); config.config_path = config_path; } else { warn!("Could not deserialise {}, recreating", config_path); config = GfxConfig::new(config_path); } } else { config = Self::new(config_path) } config.write(); config } pub fn read(&mut self) { let mut file = OpenOptions::new() .read(true) .open(&self.config_path) .unwrap_or_else(|err| panic!("Error reading {}: {}", self.config_path, err)); let mut buf = String::new(); if let Ok(l) = file.read_to_string(&mut buf) { if l == 0 { warn!("File is empty {}", self.config_path); } else { let mut x: Self = serde_json::from_str(&buf) .unwrap_or_else(|_| panic!("Could not deserialise {}", self.config_path)); // copy over serde skipped values x.tmp_mode = self.tmp_mode; *self = x; } } } pub fn write(&self) { let mut file = File::create(&self.config_path).expect("Couldn't overwrite config"); let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed"); file.write_all(json.as_bytes()) .unwrap_or_else(|err| error!("Could not write config: {}", err)); } } /// Creates the full modprobe.conf required for vfio pass-through fn create_vfio_conf(devices: &DiscreetGpu) -> Vec<u8> { let mut vifo = MODPROBE_VFIO.to_vec(); for (f_count, func) in devices.devices().iter().enumerate() { unsafe { vifo.append(func.pci_id().to_owned().as_mut_vec()); } if f_count < devices.devices().len() - 1 { vifo.append(&mut vec![b',']); } } vifo.append(&mut vec![b',']); let mut conf = MODPROBE_INTEGRATED.to_vec(); conf.append(&mut vifo); conf } pub(crate) fn check_vulkan_icd(mode: GfxMode) -> Result<(), GfxError> { let inactive_nv_icd: String = CONFIG_NVIDIA_VKICD.to_owned() + "_inactive"; info!("check_vulkan_icd: checking for Vulkan ICD profiles..."); if mode == GfxMode::Vfio || mode == GfxMode::Integrated { if std::path::Path::new(CONFIG_NVIDIA_VKICD).exists() { info!( "check_vulkan_icd: moving {} to {}", CONFIG_NVIDIA_VKICD, inactive_nv_icd.clone() ); std::fs::rename(CONFIG_NVIDIA_VKICD, inactive_nv_icd) .map_err(|err| GfxError::Write(CONFIG_NVIDIA_VKICD.to_owned(), err))?; } } else if std::path::Path::new(&inactive_nv_icd).exists() { info!( "check_vulkan_icd: moving {} to {}", inactive_nv_icd.clone(), CONFIG_NVIDIA_VKICD ); // nvidia icd must be applied std::fs::rename(inactive_nv_icd.clone(), CONFIG_NVIDIA_VKICD) .map_err(|err| GfxError::Write(inactive_nv_icd, err))?; } Ok(()) } pub(crate) fn create_modprobe_conf(mode: GfxMode, device: &DiscreetGpu) -> Result<(), GfxError> { if device.is_amd() || device.is_intel() { return Ok(()); } let content = match mode { GfxMode::Hybrid | GfxMode::AsusEgpu | GfxMode::NvidiaNoModeset => { let mut base = MODPROBE_NVIDIA_BASE.to_vec(); base.append(&mut MODPROBE_NVIDIA_DRM_MODESET_ON.to_vec()); base } GfxMode::Vfio => create_vfio_conf(device), GfxMode::Integrated => { let mut base = MODPROBE_INTEGRATED.to_vec(); base.append(&mut MODPROBE_NVIDIA_DRM_MODESET_ON.to_vec()); base } GfxMode::None | GfxMode::AsusMuxDgpu => vec![], }; let mut file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(MODPROBE_PATH) .map_err(|err| GfxError::Path(MODPROBE_PATH.into(), err))?; info!("create_modprobe_conf: writing {}", MODPROBE_PATH); file.write_all(&content) .and_then(|_| file.sync_all()) .map_err(|err| GfxError::Write(MODPROBE_PATH.into(), err))?; Ok(()) } 07070100000016000081A400000000000000000000000166FDDEC800000EBA000000000000000000000000000000000000002400000000supergfxctl-5.2.4/src/config_old.rsuse serde_derive::{Deserialize, Serialize}; use crate::{ config::GfxConfig, pci_device::{GfxMode, HotplugType}, }; #[derive(Debug, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] pub enum GfxMode300 { Hybrid, Nvidia, Integrated, Compute, Vfio, Egpu, } impl From<GfxMode300> for GfxMode { fn from(m: GfxMode300) -> Self { match m { GfxMode300::Hybrid => GfxMode::Hybrid, GfxMode300::Nvidia => GfxMode::Hybrid, GfxMode300::Integrated => GfxMode::Integrated, GfxMode300::Compute => GfxMode::Hybrid, GfxMode300::Vfio => GfxMode::Vfio, GfxMode300::Egpu => GfxMode::AsusEgpu, } } } #[derive(Deserialize, Serialize)] pub struct GfxConfig300 { pub gfx_mode: GfxMode, pub gfx_managed: bool, pub gfx_vfio_enable: bool, } impl From<GfxConfig300> for GfxConfig { fn from(old: GfxConfig300) -> Self { GfxConfig { config_path: Default::default(), mode: old.gfx_mode, tmp_mode: Default::default(), pending_mode: None, pending_action: None, vfio_enable: old.gfx_vfio_enable, vfio_save: false, always_reboot: false, no_logind: false, logout_timeout_s: 180, hotplug_type: HotplugType::None, } } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GfxConfig402 { pub mode: GfxMode, pub vfio_enable: bool, pub vfio_save: bool, pub compute_save: bool, pub always_reboot: bool, } impl From<GfxConfig402> for GfxConfig { fn from(old: GfxConfig402) -> Self { GfxConfig { config_path: Default::default(), mode: old.mode, tmp_mode: Default::default(), pending_mode: None, pending_action: None, vfio_enable: old.vfio_enable, vfio_save: old.vfio_save, always_reboot: old.always_reboot, no_logind: false, logout_timeout_s: 180, hotplug_type: HotplugType::None, } } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GfxConfig405 { pub mode: GfxMode, pub vfio_enable: bool, pub vfio_save: bool, pub compute_save: bool, pub always_reboot: bool, pub no_logind: bool, pub logout_timeout_s: u64, } impl From<GfxConfig405> for GfxConfig { fn from(old: GfxConfig405) -> Self { GfxConfig { config_path: Default::default(), mode: old.mode, tmp_mode: Default::default(), pending_mode: None, pending_action: None, vfio_enable: old.vfio_enable, vfio_save: old.vfio_save, always_reboot: old.always_reboot, no_logind: false, logout_timeout_s: 180, hotplug_type: HotplugType::None, } } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct GfxConfig500 { pub mode: GfxMode, pub vfio_enable: bool, pub vfio_save: bool, pub compute_save: bool, pub always_reboot: bool, pub no_logind: bool, pub logout_timeout_s: u64, pub hotplug_type: HotplugType, } impl From<GfxConfig500> for GfxConfig { fn from(old: GfxConfig500) -> Self { GfxConfig { config_path: Default::default(), mode: old.mode, tmp_mode: Default::default(), pending_mode: None, pending_action: None, vfio_enable: old.vfio_enable, vfio_save: old.vfio_save, always_reboot: old.always_reboot, no_logind: false, logout_timeout_s: 180, hotplug_type: HotplugType::None, } } } 07070100000017000081A400000000000000000000000166FDDEC8000023B0000000000000000000000000000000000000002400000000supergfxctl-5.2.4/src/controller.rsuse log::{debug, info, warn}; use std::{ sync::atomic::{AtomicBool, Ordering}, sync::Arc, }; use zbus::export::futures_util::lock::Mutex; use crate::{ actions::{StagedAction, UserActionRequired}, pci_device::HotplugType, }; use crate::{ error::GfxError, pci_device::{DiscreetGpu, GfxVendor, RuntimePowerManagement}, special_asus::{asus_dgpu_disable_exists, asus_egpu_enable_exists}, *, }; use super::config::GfxConfig; pub struct CtrlGraphics { pub(crate) dgpu: Arc<Mutex<DiscreetGpu>>, pub(crate) config: Arc<Mutex<GfxConfig>>, loop_exit: Arc<AtomicBool>, } impl CtrlGraphics { pub fn new(config: Arc<Mutex<GfxConfig>>) -> Result<CtrlGraphics, GfxError> { Ok(CtrlGraphics { dgpu: Arc::new(Mutex::new(DiscreetGpu::new()?)), config, loop_exit: Arc::new(AtomicBool::new(false)), }) } pub fn dgpu_arc_clone(&self) -> Arc<Mutex<DiscreetGpu>> { self.dgpu.clone() } /// Force re-init of all state, including reset of device state pub async fn reload(&mut self) -> Result<(), GfxError> { let mut config = self.config.lock().await; let vfio_enable = config.vfio_enable; let mode = get_kernel_cmdline_mode()? .map(|mode| { warn!("reload: Graphic mode {:?} set on kernel cmdline", mode); config.mode = mode; config.write(); mode }) .unwrap_or(self.get_gfx_mode(&config)?); if matches!(mode, GfxMode::Vfio) && !vfio_enable { warn!("reload: Tried to set vfio mode but it is not enabled"); return Ok(()); } if matches!(mode, GfxMode::AsusEgpu) && !asus_egpu_enable_exists() { warn!("reload: Tried to set egpu mode but it is not supported"); return Ok(()); } let mut dgpu = self.dgpu.lock().await; Self::do_boot_tasks(mode, &mut config, &mut dgpu).await?; info!("reload: Reloaded gfx mode: {:?}", mode); Ok(()) } /// Associated method to get which mode is set pub(crate) fn get_gfx_mode(&self, config: &GfxConfig) -> Result<GfxMode, GfxError> { if let Some(mode) = config.tmp_mode { dbg!(&mode); return Ok(mode); } Ok(config.mode) } /// pub(crate) async fn get_pending_mode(&self) -> GfxMode { let config = self.config.lock().await; if let Some(mode) = config.pending_mode { return mode; } GfxMode::None } /// pub(crate) async fn get_pending_user_action(&self) -> UserActionRequired { let config = self.config.lock().await; if let Some(action) = config.pending_action { return action; } UserActionRequired::Nothing } /// Associated method to get list of supported modes pub(crate) async fn get_supported_modes(&self) -> Vec<GfxMode> { let mut list = vec![GfxMode::Integrated, GfxMode::Hybrid]; let dgpu = self.dgpu.lock().await; if matches!(dgpu.vendor(), GfxVendor::Unknown) && !asus_dgpu_disable_exists() { return vec![GfxMode::Integrated]; } let config = self.config.lock().await; if config.vfio_enable { list.push(GfxMode::Vfio); } if asus_egpu_enable_exists() { list.push(GfxMode::AsusEgpu); } if asus_gpu_mux_exists() { list.push(GfxMode::AsusMuxDgpu); } if let Ok(Some(res)) = get_kernel_cmdline_nvidia_modeset() { if !res { list.push(GfxMode::NvidiaNoModeset); } } list } /// Associated method to get which vendor the dgpu is from pub(crate) async fn get_gfx_vendor(&self) -> GfxVendor { let dgpu = self.dgpu.lock().await; dgpu.vendor() } /// Perform boot tasks required to set last saved mode async fn do_boot_tasks( mut mode: GfxMode, config: &mut GfxConfig, device: &mut DiscreetGpu, ) -> Result<(), GfxError> { debug!( "do_mode_setup_tasks(mode:{mode:?}, vfio_enable:{}, asus_use_dgpu_disable: {:?})", config.vfio_enable, config.hotplug_type ); // Absolutely must check the ASUS dgpu_disable and gpu mux sanity on boot if let Ok(checked_mode) = asus_boot_safety_check(mode, config.hotplug_type == HotplugType::Asus) .await .map_err(|e| { error!("asus_boot_safety_check errored: {e}"); }) { config.mode = checked_mode; mode = checked_mode; } let loop_exit = Arc::new(AtomicBool::new(false)); let actions = StagedAction::action_list_for_boot(config, device.vendor(), mode); for action in actions { let res = action.perform(mode, device, loop_exit.clone()).await; match res { Ok(_) => {} Err(e) => error!("Action thread errored: {e}"), } } device.set_runtime_pm(RuntimePowerManagement::Auto)?; Ok(()) } /// Initiates a mode change by starting a thread that will wait until all /// graphical sessions are exited before performing the tasks required /// to switch modes. /// /// For manually calling (not on boot/startup) via dbus pub async fn set_gfx_mode(&mut self, mode: GfxMode) -> Result<UserActionRequired, GfxError> { mode_support_check(&mode)?; self.loop_exit.store(false, Ordering::Release); let vendor = self.dgpu.lock().await.vendor(); let user_action_required; let actions; { let mut config = self.config.lock().await; let from = config.mode; if config.always_reboot { user_action_required = UserActionRequired::Reboot; } else { user_action_required = UserActionRequired::mode_change_action(mode, config.mode); } actions = StagedAction::action_list_for_switch(&config, vendor, from, mode); config.pending_mode = Some(mode); config.pending_action = Some(user_action_required); } // Start a thread to perform the actions on then return the user action required // First, stop all threads self.loop_exit.store(true, Ordering::Release); match actions { actions::Action::UserAction(u) => return Ok(u), actions::Action::StagedActions(actions) => { let dgpu = self.dgpu.clone(); // This atomixc is to force an exit of any loops let loop_exit = self.loop_exit.clone(); let config = self.config.clone(); // This will block if required to wait for logouts, so run concurrently. tokio::spawn(async move { let mut failed = false; for action in actions { debug!("Doing action: {action:?}"); let mut dgpu = dgpu.lock().await; let res = action.perform(mode, &mut dgpu, loop_exit.clone()).await; match res { Ok(_) => {} Err(GfxError::SystemdUnitWaitTimeout(e)) => { error!("Action thread errored: {e}"); failed = true; break; } Err(e) => { error!("Action thread errored: {e}"); failed = true; } } } let mut config = config.lock().await; config.pending_mode = None; config.pending_action = None; if !failed { config.mode = mode; config.write(); } else { let from = config.mode; let actions = StagedAction::action_list_for_switch(&config, vendor, mode, from); if let actions::Action::StagedActions(actions) = actions { for action in actions { debug!("Doing action: {action:?}"); let mut dgpu = dgpu.lock().await; if let Err(e) = action.perform(mode, &mut dgpu, loop_exit.clone()).await { error!("Action thread errored fallback failed: {e}"); return; } } } } }); } } Ok(user_action_required) } } 07070100000018000081A400000000000000000000000166FDDEC8000013B1000000000000000000000000000000000000002000000000supergfxctl-5.2.4/src/daemon.rsuse std::{env, sync::Arc, time::Duration}; use log::{error, info, trace}; use logind_zbus::manager::ManagerProxy; use std::io::Write; use supergfxctl::{ config::GfxConfig, controller::CtrlGraphics, error::GfxError, pci_device::{DiscreetGpu, GfxMode, GfxPower, HotplugType}, special_asus::{asus_dgpu_disable_exists, asus_dgpu_set_disabled}, CONFIG_PATH, DBUS_DEST_NAME, DBUS_IFACE_PATH, VERSION, }; use tokio::time::sleep; use zbus::zvariant::ObjectPath; use zbus::{ export::futures_util::{lock::Mutex, StreamExt}, Connection, SignalContext, }; #[tokio::main] async fn main() -> Result<(), GfxError> { let mut logger = env_logger::Builder::new(); logger .parse_default_env() .target(env_logger::Target::Stdout) .format(|buf, record| writeln!(buf, "{}: {}", record.level(), record.args())) .init(); let is_service = match env::var_os("IS_SERVICE") { Some(val) => val == "1", None => false, }; if !is_service { println!("supergfxd schould be only run from the right systemd service"); println!( "do not run in your terminal, if you need an logs please use journalctl -b -u supergfxd" ); println!("supergfxd will now exit"); return Ok(()); } info!("Daemon version: {VERSION}"); start_daemon().await } async fn start_daemon() -> Result<(), GfxError> { // Start zbus server let connection = Connection::system().await?; // Request dbus name after finishing initalizing all functions connection.request_name(DBUS_DEST_NAME).await?; let config = GfxConfig::load(CONFIG_PATH.into()); let use_logind = !config.no_logind; let config = Arc::new(Mutex::new(config)); if use_logind { start_logind_tasks(config.clone()).await; } // Graphics switching requires some checks on boot specifically for g-sync capable laptops match CtrlGraphics::new(config.clone()) { Ok(mut ctrl) => { ctrl.reload() .await .unwrap_or_else(|err| error!("Gfx controller: {}", err)); let signal_context = SignalContext::new(&connection, DBUS_IFACE_PATH)?; start_notify_status(ctrl.dgpu_arc_clone(), signal_context) .await .ok(); connection .object_server() .at(&ObjectPath::from_str_unchecked(DBUS_IFACE_PATH), ctrl) .await // .map_err(|err| { // warn!("{}: add_to_server {}", path, err); // err // }) .ok(); } Err(err) => { error!("Gfx control: {}", err); } } // Request dbus name after finishing initalizing all functions connection.request_name(DBUS_DEST_NAME).await?; // Loop to check errors and iterate zbus server loop { sleep(Duration::from_secs(1)).await; } } async fn start_notify_status( dgpu: Arc<Mutex<DiscreetGpu>>, signal_ctxt: SignalContext<'static>, ) -> Result<(), GfxError> { tokio::spawn(async move { let mut last_status = GfxPower::Unknown; loop { let s = dgpu .lock() .await .get_runtime_status() .map_err(|e| trace!("{e}")) .unwrap_or(GfxPower::Unknown); if s != last_status { last_status = s; trace!("Notify: dGPU status = {s:?}"); CtrlGraphics::notify_gfx_status(&signal_ctxt, &last_status) .await .map_err(|e| trace!("{e}")) .ok(); } sleep(Duration::from_secs(1)).await; } }); Ok(()) } async fn start_logind_tasks(config: Arc<Mutex<GfxConfig>>) { let connection = Connection::system() .await .expect("Controller could not create dbus connection"); let manager = ManagerProxy::new(&connection) .await .expect("Controller could not create ManagerProxy"); tokio::spawn(async move { if let Ok(mut notif) = manager.receive_prepare_for_sleep().await { while let Some(event) = notif.next().await { if let Ok(args) = event.args() { if !args.start() { // on_wake(); let config = config.lock().await; if config.mode == GfxMode::Integrated && config.hotplug_type == HotplugType::Asus && asus_dgpu_disable_exists() { info!("logind task: Waking from suspend, setting dgpu_disable"); asus_dgpu_set_disabled(true) .map_err(|e| error!("logind task: {e}")) .ok(); } } } } } }); } 07070100000019000081A400000000000000000000000166FDDEC800000F81000000000000000000000000000000000000001F00000000supergfxctl-5.2.4/src/error.rsuse std::fmt; use std::{error, path::PathBuf}; use crate::actions::StagedAction; #[derive(Debug)] pub enum GfxError { ParseVendor, ParseMode, DgpuNotFound, Udev(String, std::io::Error), SystemdUnitAction(String), SystemdUnitWaitTimeout(String), AsusGpuMuxModeDiscreet, VfioBuiltin, VfioDisabled, MissingModule(String), Modprobe(String), Command(String, std::io::Error), Path(String, std::io::Error), Read(String, std::io::Error), Write(String, std::io::Error), NotSupported(String), Io(PathBuf, std::io::Error), Zbus(zbus::Error), ZbusFdo(zbus::fdo::Error), /// `IncorrectActionOrder(this_action, last_action)` IncorrectActionOrder(StagedAction, StagedAction), } impl GfxError { pub fn from_io(error: std::io::Error, detail: PathBuf) -> Self { Self::Io(detail, error) } } impl fmt::Display for GfxError { // This trait requires `fmt` with this exact signature. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { GfxError::ParseVendor => write!(f, "Could not parse vendor name"), GfxError::ParseMode => write!(f, "Could not parse mode name"), GfxError::DgpuNotFound => write!( f, "Didn't find dgpu. If this is an ASUS ROG/TUF laptop this is okay" ), GfxError::Udev(msg, err) => write!(f, "udev: {msg}: {err}"), GfxError::SystemdUnitAction(action) => { write!(f, "systemd unit action {} failed", action) } GfxError::SystemdUnitWaitTimeout(state) => { write!( f, "Timed out waiting for systemd unit change {} state", state ) } GfxError::AsusGpuMuxModeDiscreet => write!( f, "Can not switch gfx modes when discreet/G-Sync mode is active" ), GfxError::VfioBuiltin => write!( f, "Can not switch to vfio mode if the modules are built in to kernel" ), GfxError::VfioDisabled => { write!(f, "Can not switch to vfio mode if disabled in config file") } GfxError::MissingModule(m) => write!(f, "The module {} is missing", m), GfxError::Modprobe(detail) => write!(f, "Modprobe error: {}", detail), GfxError::Command(func, error) => write!(f, "Command exec error: {}: {}", func, error), GfxError::Path(path, error) => write!(f, "Path {}: {}", path, error), GfxError::Read(path, error) => write!(f, "Read {}: {}", path, error), GfxError::Write(path, error) => write!(f, "Write {}: {}", path, error), GfxError::NotSupported(path) => write!(f, "{}", path), GfxError::Io(detail, error) => { if detail.clone().into_os_string().is_empty() { write!(f, "std::io error: {}", error) } else { write!(f, "std::io error: {}, {}", error, detail.display()) } } GfxError::Zbus(detail) => write!(f, "Zbus error: {}", detail), GfxError::ZbusFdo(detail) => write!(f, "Zbus error: {}", detail), GfxError::IncorrectActionOrder(this_action, last_action) => write!( f, "The order of actions is incorrect: {last_action:?} should not be before {this_action:?}" ), } } } impl error::Error for GfxError {} impl From<zbus::Error> for GfxError { fn from(err: zbus::Error) -> Self { GfxError::Zbus(err) } } impl From<zbus::fdo::Error> for GfxError { fn from(err: zbus::fdo::Error) -> Self { GfxError::ZbusFdo(err) } } impl From<std::io::Error> for GfxError { fn from(err: std::io::Error) -> Self { GfxError::Io(PathBuf::new(), err) } } 0707010000001A000081A400000000000000000000000166FDDEC8000025C1000000000000000000000000000000000000001D00000000supergfxctl-5.2.4/src/lib.rsuse std::{ fs::OpenOptions, io::Read, path::{Path, PathBuf}, process::Command, str::FromStr, }; use log::{debug, error, info, warn}; use pci_device::GfxVendor; use crate::{error::GfxError, pci_device::GfxMode, special_asus::*}; /// The configuration for graphics. This should be saved and loaded on boot. pub mod config; mod config_old; /// Control functions for setting graphics. pub mod controller; /// Error: 404 pub mod error; /// Special-case functions for check/read/write of key functions on unique laptops /// such as the G-Sync mode available on some ASUS ROG laptops pub mod special_asus; /// Defined DBUS Interface for supergfxctl pub mod zbus_iface; /// Defined DBUS Proxy for supergfxctl pub mod zbus_proxy; /// System interface helpers. pub mod pci_device; /// Systemd helpers pub mod systemd; /// The actual actions that supergfx uses for each step pub mod actions; #[cfg(test)] mod tests; /// Helper to expose the current crate version to external code pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// Generic path that is used to save the daemon config state pub const CONFIG_PATH: &str = "/etc/supergfxd.conf"; /// Destination name to be used in the daemon when setting up DBUS connection pub const DBUS_DEST_NAME: &str = "org.supergfxctl.Daemon"; /// Generic icd-profile (vulkan) pub const CONFIG_NVIDIA_VKICD: &str = "/usr/share/vulkan/icd.d/nvidia_icd.json"; /// Interface path name. Should be common across daemon and client. pub const DBUS_IFACE_PATH: &str = "/org/supergfxctl/Gfx"; pub const KERNEL_CMDLINE: &str = "/proc/cmdline"; const SLOTS: &str = "/sys/bus/pci/slots"; const NVIDIA_DRIVERS: [&str; 4] = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"]; const VFIO_DRIVERS: [&str; 6] = [ "vfio_pci", "vfio_pci_core", "vfio_iommu_type1", "vfio_virqfd", "vfio_mdev", "vfio", ]; const DISPLAY_MANAGER: &str = "display-manager.service"; const MODPROBE_PATH: &str = "/etc/modprobe.d/supergfxd.conf"; static MODPROBE_NVIDIA_BASE: &[u8] = br#"# Automatically generated by supergfxd blacklist nouveau alias nouveau off "#; static MODPROBE_NVIDIA_DRM_MODESET_ON: &[u8] = br#" options nvidia-drm modeset=1 "#; // static MODPROBE_NVIDIA_DRM_MODESET_OFF: &[u8] = br#" // options nvidia-drm modeset=0 // "#; static MODPROBE_INTEGRATED: &[u8] = br#"# Automatically generated by supergfxd blacklist nouveau blacklist nvidia_drm blacklist nvidia_uvm blacklist nvidia_modeset blacklist nvidia "#; static MODPROBE_VFIO: &[u8] = br#"options vfio-pci ids="#; #[derive(Debug, Clone, Copy)] pub enum DriverAction { Remove, Load, } impl From<DriverAction> for &str { fn from(a: DriverAction) -> Self { match a { DriverAction::Remove => "rmmod", DriverAction::Load => "modprobe", } } } /// Basic check for support. If `()` returned everything is kosher. fn mode_support_check(mode: &GfxMode) -> Result<(), GfxError> { if matches!(mode, GfxMode::AsusEgpu) && !asus_egpu_enable_exists() { let text = "Egpu mode requested when either the laptop doesn't support it or the kernel is not recent enough".to_string(); return Err(GfxError::NotSupported(text)); } Ok(()) } /// Add or remove driver modules fn do_driver_action(driver: &str, action: DriverAction) -> Result<(), GfxError> { let mut cmd = Command::new(<&str>::from(action)); cmd.arg(driver); let mut count = 0; const MAX_TRIES: i32 = 6; loop { if count > MAX_TRIES { let msg = format!( "{} {} failed for unknown reason", <&str>::from(action), driver ); error!("{}", msg); break; //Err(GfxError::Modprobe(msg)); } let output = cmd .output() .map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?; if !output.status.success() { if output .stderr .ends_with("is not currently loaded\n".as_bytes()) { debug!( "Driver {driver} was not loaded, skipping {}", <&str>::from(action) ); break; } if output.stderr.ends_with("is builtin.\n".as_bytes()) { return Err(GfxError::VfioBuiltin); } if output.stderr.ends_with("Permission denied\n".as_bytes()) { warn!( "{} {} failed: {:?}", <&str>::from(action), driver, String::from_utf8_lossy(&output.stderr) ); warn!("It may be safe to ignore the above error, run `lsmod |grep {}` to confirm modules loaded", driver); break; } if String::from_utf8_lossy(&output.stderr) .contains(&format!("Module {} not found", driver)) { return Err(GfxError::MissingModule(driver.into())); } if count >= MAX_TRIES { let msg = format!( "{} {} failed: {:?}", <&str>::from(action), driver, String::from_utf8_lossy(&output.stderr) ); return Err(GfxError::Modprobe(msg)); } } else if output.status.success() { debug!("Did {} for driver {driver}", <&str>::from(action)); break; } count += 1; std::thread::sleep(std::time::Duration::from_millis(50)); } Ok(()) } pub fn toggle_nvidia_powerd(run: bool, vendor: GfxVendor) -> Result<(), GfxError> { if vendor == GfxVendor::Nvidia { let mut cmd = Command::new("systemctl"); if run { cmd.arg("start"); } else { cmd.arg("stop"); } cmd.arg("nvidia-powerd.service"); let status = cmd.status()?; if !status.success() { warn!("{run} nvidia-powerd.service failed: {:?}", status.code()); } debug!("Did {:?}", cmd.get_args()); } Ok(()) } pub fn kill_nvidia_lsof() -> Result<(), GfxError> { if !PathBuf::from("/dev/nvidia0").exists() { return Ok(()); } if !PathBuf::from("/usr/bin/lsof").exists() { warn!("The lsof util is missing from your system, please ensure it is available so processes hogging Nvidia can be nuked"); return Ok(()); } let mut cmd = Command::new("lsof"); cmd.arg("/dev/nvidia0"); let output = cmd .output() .map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?; let st = String::from_utf8_lossy(&output.stdout); for line in st.lines() { let mut split = line.split_whitespace(); if let Some(c) = split.next() { if let Some(pid) = split.next() { if let Ok(pid) = pid.parse::<u32>() { warn!("pid {pid} ({c}) is holding /dev/nvidia0. Killing"); let mut cmd = Command::new("kill"); cmd.arg("-9"); cmd.arg(format!("{pid}")); let status = cmd .status() .map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?; if !status.success() { warn!("Killing pid {pid} failed"); } } } } } Ok(()) } pub fn get_kernel_cmdline_mode() -> Result<Option<GfxMode>, GfxError> { let path = Path::new(KERNEL_CMDLINE); let mut file = OpenOptions::new() .read(true) .open(path) .map_err(|err| GfxError::Path(KERNEL_CMDLINE.to_string(), err))?; let mut buf = String::new(); file.read_to_string(&mut buf)?; // No need to be fast here, just check and go for cmd in buf.split(' ') { if cmd.contains("supergfxd.mode=") { let mode = cmd.trim_start_matches("supergfxd.mode="); let mode = GfxMode::from_str(mode)?; return Ok(Some(mode)); } } info!("supergfxd.mode not set, ignoring"); Ok(None) } pub fn get_kernel_cmdline_nvidia_modeset() -> Result<Option<bool>, GfxError> { let path = Path::new(KERNEL_CMDLINE); let mut file = OpenOptions::new() .read(true) .open(path) .map_err(|err| GfxError::Path(KERNEL_CMDLINE.to_string(), err))?; let mut buf = String::new(); file.read_to_string(&mut buf)?; // No need to be fast here, just check and go for cmd in buf.split(' ') { if cmd.contains("nvidia-drm.modeset=") { let mode = cmd.trim_start_matches("nvidia-drm.modeset="); let mode = mode == "1"; return Ok(Some(mode)); } } info!("nvidia-drm.modeset not set, ignoring"); Ok(None) } pub fn find_slot_power(address: &str) -> Result<PathBuf, GfxError> { let mut buf = Vec::new(); let path = PathBuf::from_str(SLOTS).unwrap(); for path in path.read_dir()? { let path = path.unwrap().path(); let mut address_path = path.to_path_buf(); address_path.push("address"); let mut file = OpenOptions::new().read(true).open(&address_path)?; file.read_to_end(&mut buf)?; if address.contains(String::from_utf8_lossy(&buf).trim_end()) { address_path.pop(); address_path.push("power"); info!("Found hotplug power slot at {:?}", address_path); return Ok(address_path); } buf.clear(); } Err(GfxError::DgpuNotFound) } 0707010000001B000081A400000000000000000000000166FDDEC800005DD7000000000000000000000000000000000000002400000000supergfxctl-5.2.4/src/pci_device.rsuse log::{debug, info, trace, warn}; use std::fmt::Display; use std::fs::{self, OpenOptions}; use std::io::{Read, Write}; use std::process::Command; use std::str::FromStr; use std::{fs::write, path::PathBuf}; use crate::error::GfxError; use crate::special_asus::{ asus_dgpu_disable_exists, asus_dgpu_disabled, asus_gpu_mux_exists, asus_gpu_mux_mode, AsusGpuMuxMode, }; use crate::{do_driver_action, find_slot_power, DriverAction, NVIDIA_DRIVERS}; use serde_derive::{Deserialize, Serialize}; use zbus::zvariant::Type; const PCI_BUS_PATH: &str = "/sys/bus/pci"; #[derive(Debug, Type, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] pub enum HotplugType { /// Use only kernel level hotplug feature Std, /// Use ASUS dgpu_disable Asus, /// Do not use hotplugging None, } #[derive(Debug, Type, PartialEq, Eq, Copy, Clone)] pub enum HotplugState { On, Off, } impl FromStr for HotplugState { type Err = GfxError; fn from_str(s: &str) -> Result<Self, GfxError> { match s.to_lowercase().trim() { "1" => Ok(Self::On), _ => Ok(Self::Off), } } } impl From<HotplugState> for &str { fn from(gfx: HotplugState) -> &'static str { match gfx { HotplugState::On => "1", HotplugState::Off => "0", } } } #[derive(Debug, Default, Type, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] pub enum GfxPower { Active, Suspended, Off, AsusDisabled, AsusMuxDiscreet, #[default] Unknown, } impl FromStr for GfxPower { type Err = GfxError; fn from_str(s: &str) -> Result<Self, GfxError> { Ok(match s.to_lowercase().trim() { "active" => GfxPower::Active, "suspended" => GfxPower::Suspended, "off" => GfxPower::Off, "dgpu_disabled" => GfxPower::AsusDisabled, "asus_mux_discreet" => GfxPower::AsusMuxDiscreet, _ => GfxPower::Unknown, }) } } impl From<&GfxPower> for &str { fn from(gfx: &GfxPower) -> &'static str { match gfx { GfxPower::Active => "active", GfxPower::Suspended => "suspended", GfxPower::Off => "off", GfxPower::AsusDisabled => "dgpu_disabled", GfxPower::AsusMuxDiscreet => "asus_mux_discreet", GfxPower::Unknown => "unknown", } } } #[derive(Debug, Type, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] pub enum GfxVendor { Nvidia, Amd, Intel, Unknown, AsusDgpuDisabled, } impl From<u16> for GfxVendor { fn from(vendor: u16) -> Self { match vendor { 0x1002 => GfxVendor::Amd, 0x10DE => GfxVendor::Nvidia, 0x8086 => GfxVendor::Intel, _ => GfxVendor::Unknown, } } } impl From<&str> for GfxVendor { fn from(vendor: &str) -> Self { match vendor { "0x1002" => GfxVendor::Amd, "0x10DE" => GfxVendor::Nvidia, "0x8086" => GfxVendor::Intel, "1002" => GfxVendor::Amd, "10DE" => GfxVendor::Nvidia, "8086" => GfxVendor::Intel, _ => GfxVendor::Unknown, } } } impl From<GfxVendor> for &str { fn from(vendor: GfxVendor) -> Self { match vendor { GfxVendor::Nvidia => "Nvidia", GfxVendor::Amd => "AMD", GfxVendor::Intel => "Intel", GfxVendor::Unknown => "Unknown", GfxVendor::AsusDgpuDisabled => "ASUS dGPU disabled", } } } impl From<&GfxVendor> for &str { fn from(vendor: &GfxVendor) -> Self { <&str>::from(*vendor) } } /// All the available modes. Every mode except `None` and `AsusMuxDgpu` should assume that either /// the ASUS specific `gpu_mux_mode` sysfs entry is not available or is set to iGPU mode. #[derive(Debug, Default, Type, PartialEq, Eq, Copy, Clone, Deserialize, Serialize)] pub enum GfxMode { Hybrid, Integrated, /// This mode is for folks using `nomodeset=0` on certain hardware. It allows hot unloading of nvidia NvidiaNoModeset, Vfio, /// The ASUS EGPU is in use AsusEgpu, /// The ASUS GPU MUX is set to dGPU mode AsusMuxDgpu, #[default] None, } impl Display for GfxMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Hybrid => write!(f, "{:?}", &self), Self::Integrated => write!(f, "{:?}", &self), Self::NvidiaNoModeset => write!(f, "{:?}", &self), Self::Vfio => write!(f, "{:?}", &self), Self::AsusEgpu => write!(f, "{:?}", &self), Self::AsusMuxDgpu => write!(f, "{:?}", &self), Self::None => write!(f, "Unknown"), } } } impl FromStr for GfxMode { type Err = GfxError; fn from_str(s: &str) -> Result<Self, GfxError> { match s.trim() { "Hybrid" => Ok(GfxMode::Hybrid), "Integrated" => Ok(GfxMode::Integrated), "NvidiaNoModeset" => Ok(GfxMode::NvidiaNoModeset), "Vfio" => Ok(GfxMode::Vfio), "AsusEgpu" => Ok(GfxMode::AsusEgpu), "AsusMuxDgpu" => Ok(GfxMode::AsusMuxDgpu), _ => Err(GfxError::ParseMode), } } } /// Will rescan the device tree, which adds all removed devices back pub fn rescan_pci_bus() -> Result<(), GfxError> { let path = PathBuf::from(PCI_BUS_PATH).join("rescan"); write(&path, "1").map_err(|e| GfxError::from_io(e, path)) } fn lscpi(vendor_device: &str) -> Result<String, GfxError> { let mut cmd = Command::new("lspci"); cmd.args(["-d", vendor_device]); let s = String::from_utf8_lossy(&cmd.output()?.stdout).into_owned(); Ok(s) } pub fn lscpi_dgpu_check(label: &str) -> bool { for pat in [ "Radeon RX", "AMD/ATI", "GeForce", "Geforce", "Quadro", "T1200", ] { if label.contains(pat) { return true; } } false } #[derive(Clone, Debug)] pub struct Device { /// Concrete path to the device control dev_path: PathBuf, /// Concrete path to the slot this device is in for hotplug support hotplug_path: Option<PathBuf>, vendor: GfxVendor, is_dgpu: bool, /// System name given by kerne, e.g `0000:01:00.0` name: String, /// Vendor:Device, typically used only for VFIO setup pci_id: String, } impl Device { pub fn dev_path(&self) -> &PathBuf { &self.dev_path } pub fn vendor(&self) -> GfxVendor { self.vendor } pub fn is_dgpu(&self) -> bool { self.is_dgpu } pub fn pci_id(&self) -> &str { &self.pci_id } fn set_hotplug(&self, state: HotplugState) -> Result<(), GfxError> { if let Some(path) = self.hotplug_path.as_ref() { info!("set_hotplug: Setting hotplug power to {state:?}"); let mut file = OpenOptions::new() .write(true) .open(path) .map_err(|err| GfxError::Path(path.to_string_lossy().to_string(), err))?; file.write_all(<&str>::from(state).as_bytes()) .map_err(|err| GfxError::Write(path.to_string_lossy().to_string(), err))?; } Ok(()) } pub fn find() -> Result<Vec<Self>, GfxError> { let mut devices = Vec::new(); let mut parent = String::new(); let mut enumerator = udev::Enumerator::new().map_err(|err| { warn!("{}", err); GfxError::Udev("enumerator failed".into(), err) })?; enumerator.match_subsystem("pci").map_err(|err| { warn!("{}", err); GfxError::Udev("match_subsystem failed".into(), err) })?; let get_parent = |dev: &udev::Device| -> String { dev.sysname() .to_string_lossy() .trim_end_matches(char::is_numeric) .trim_end_matches('.') .to_string() }; for device in enumerator.scan_devices().map_err(|err| { warn!("{}", err); GfxError::Udev("scan_devices failed".into(), err) })? { let sysname = device.sysname().to_string_lossy(); debug!("Looking at PCI device {:?}", sysname); // PCI_ID can be given directly to lspci to get a database label // This is the same as ID_MODEL_FROM_DATABASE if let Some(id) = device.property_value("PCI_ID") { if let Some(class) = device.property_value("PCI_CLASS") { let id = id.to_string_lossy(); // class can be 0x030200 or 0x030000 let class = class.to_string_lossy(); // Match only Nvidia or AMD if id.starts_with("10DE") || id.starts_with("1002") { if let Some(vendor) = id.split(':').next() { // DGPU CHECK // Assumes that the enumeration is always in order, so things on the same bus after the dGPU // are attached. Look at parent system name to match let dgpu = if let Some(boot_vga) = device.attribute_value("boot_vga") { if boot_vga == "0" { debug!("Found non-boot_vga {id} at {:?}", device.sysname()); } else { debug!("Found boot_vga {id} at {:?}", device.sysname()); } // Sometimes Nvidia dGPU gets boot_vga == "1" in Hybrid mode // Assume all Nvidia GPUs are dGPU class.starts_with("30") && (boot_vga == "0" || id.starts_with("10DE")) } else if id.starts_with("1002") { debug!("Found AMD GPU {id} without boot_vga attribute at {:?}", device.sysname()); // Sometimes AMD iGPU doesn't get a boot_vga attribute even in Hybrid mode // Fallback to the following method for telling iGPU apart from dGPU: // https://github.com/fastfetch-cli/fastfetch/blob/fed2c87f67de43e3672d1a4a7767d59e7ff22ba2/src/detection/gpu/gpu_linux.c#L148 let mut dev_path = PathBuf::from(device.syspath()); dev_path.push("hwmon"); let hwmon_n_opt = dev_path.read_dir().map_err( |e| GfxError::from_io(e, dev_path) )?.next(); match hwmon_n_opt { Some(hwmon_n_result) => { let mut hwmon_n = hwmon_n_result?.path(); hwmon_n.push("in1_input"); !hwmon_n.exists() } None => false } } else if let Some(label) = device.property_value("ID_MODEL_FROM_DATABASE") { debug!( "Found ID_MODEL_FROM_DATABASE property {id} at {:?} : {label:?}", device.sysname() ); lscpi_dgpu_check(&label.to_string_lossy()) } else { // last resort - this is typically only required if ID_MODEL_FROM_DATABASE is // missing due to dgpu_disable being on at boot debug!("Didn't find dGPU with standard methods, using last resort for id:{id} at {:?}", device.sysname()); lscpi_dgpu_check(&lscpi(&id)?) }; if dgpu || !parent.is_empty() && sysname.contains(&parent) { let mut hotplug_path = None; if dgpu { info!("Found dgpu {id} at {:?}", device.sysname()); match find_slot_power(&sysname) { Ok(slot) => hotplug_path = Some(slot), Err(e) => { if let Ok(c) = asus_gpu_mux_mode() { debug!( "Laptop is in dGPU MUX mode? {}", c == AsusGpuMuxMode::Discreet ); } else { debug!( "Laptop does not have a hotplug dgpu: {e:?}" ); } } } } else { info!("Found additional device {id} at {:?}", device.sysname()); } parent = get_parent(&device); devices.push(Self { dev_path: PathBuf::from(device.syspath()), hotplug_path, vendor: vendor.into(), is_dgpu: dgpu, name: sysname.to_string(), pci_id: id.to_string(), }); } } } } } if !parent.is_empty() && !sysname.contains(&parent) { break; } } if devices.is_empty() { return Err(GfxError::DgpuNotFound); } Ok(devices) } /// Read a file underneath the sys object fn read_file(path: PathBuf) -> Result<String, GfxError> { let path = path.canonicalize()?; let mut data = String::new(); let mut file = fs::OpenOptions::new() .read(true) .open(&path) .map_err(|e| GfxError::from_io(e, path.clone()))?; trace!("read_file: {file:?}"); file.read_to_string(&mut data) .map_err(|e| GfxError::from_io(e, path))?; Ok(data) } /// Write a file underneath the sys object fn write_file(path: PathBuf, data: &[u8]) -> Result<(), GfxError> { let path = path.canonicalize()?; let mut file = fs::OpenOptions::new() .write(true) .open(&path) .map_err(|e| GfxError::from_io(e, path.clone()))?; trace!("write_file: {file:?}"); file.write_all(data.as_ref()) .map_err(|e| GfxError::from_io(e, path))?; Ok(()) } pub fn set_runtime_pm(&self, state: RuntimePowerManagement) -> Result<(), GfxError> { let mut path = self.dev_path.clone(); path.push("power"); path.push("control"); if path.exists() { trace!("set_runtime_pm: {path:?}"); Self::write_file(path, <&str>::from(state).as_bytes())?; } else { debug!("set_runtime_pm: {path:?} doesn't exist, device may have been removed (can be ignored)"); } Ok(()) } pub fn get_runtime_status(&self) -> Result<GfxPower, GfxError> { let mut path = self.dev_path.clone(); path.push("power"); path.push("runtime_status"); trace!("get_runtime_status: {path:?}"); match Self::read_file(path) { Ok(inner) => GfxPower::from_str(inner.as_str()), Err(_) => Ok(GfxPower::Off), } } pub fn driver(&self) -> std::io::Result<PathBuf> { fs::canonicalize(self.dev_path.join("driver")) } pub fn unbind(&self) -> Result<(), GfxError> { if let Ok(mut path) = self.driver() { if path.exists() { path.push("unbind"); return Self::write_file(path, self.name.as_bytes()); } } info!( "unbind path {:?} did not exist, driver unloaded?", self.dev_path ); Ok(()) } pub fn remove(&self) -> Result<(), GfxError> { if self.dev_path.exists() { let mut path = self.dev_path.clone(); path.push("remove"); return Self::write_file(path, "1".as_bytes()); } info!( "remove path {:?} did not exist, device removed already?", self.dev_path ); Ok(()) } } /// Control whether a device uses, or does not use, runtime power management. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum RuntimePowerManagement { Auto, On, Off, } impl From<RuntimePowerManagement> for &'static str { fn from(pm: RuntimePowerManagement) -> &'static str { match pm { RuntimePowerManagement::Auto => "auto", RuntimePowerManagement::On => "on", RuntimePowerManagement::Off => "off", } } } impl From<&str> for RuntimePowerManagement { fn from(pm: &str) -> RuntimePowerManagement { match pm { "auto" => RuntimePowerManagement::Auto, "on" => RuntimePowerManagement::On, "off" => RuntimePowerManagement::Off, _ => RuntimePowerManagement::On, } } } /// Collection of all graphics devices. Functions intend to work on the device /// determined to be the discreet GPU only. #[derive(Clone)] pub struct DiscreetGpu { vendor: GfxVendor, dgpu_index: usize, devices: Vec<Device>, } impl DiscreetGpu { pub fn new() -> Result<DiscreetGpu, GfxError> { info!("DiscreetGpu::new: Rescanning PCI bus"); rescan_pci_bus()?; if let Ok(device) = Device::find() { let mut vendor = GfxVendor::Unknown; let mut dgpu_index = 0; for (idx, dev) in device.iter().enumerate() { if dev.is_dgpu() { dgpu_index = idx; vendor = dev.vendor(); } } Ok(Self { vendor, dgpu_index, devices: device, }) } else { warn!("DiscreetGpu::new: no devices??"); let mut vendor = GfxVendor::Unknown; if asus_dgpu_disable_exists() && if let Ok(c) = asus_dgpu_disabled() { c } else { false } { warn!("ASUS dGPU appears to be disabled"); vendor = GfxVendor::AsusDgpuDisabled; } else if asus_gpu_mux_exists() && if let Ok(c) = asus_gpu_mux_mode() { c == AsusGpuMuxMode::Discreet } else { false } { warn!("ASUS GPU MUX is in discreet mode"); vendor = GfxVendor::Nvidia; } Ok(Self { vendor, dgpu_index: 0, devices: Vec::new(), }) } } pub fn vendor(&self) -> GfxVendor { self.vendor } pub fn devices(&self) -> &[Device] { &self.devices } pub fn is_nvidia(&self) -> bool { self.vendor == GfxVendor::Nvidia } pub fn is_amd(&self) -> bool { self.vendor == GfxVendor::Amd } pub fn is_intel(&self) -> bool { self.vendor == GfxVendor::Intel } pub fn get_runtime_status(&self) -> Result<GfxPower, GfxError> { if !self.devices.is_empty() { trace!("get_runtime_status: {:?}", self.devices[self.dgpu_index]); if self.vendor == GfxVendor::AsusDgpuDisabled { //warn!("ASUS dgpu status: {:?}", self.vendor); return Ok(GfxPower::AsusDisabled); } else if self.vendor != GfxVendor::Unknown { return self.devices[self.dgpu_index].get_runtime_status(); } } else if asus_dgpu_disable_exists() { if let Ok(disabled) = asus_dgpu_disabled() { trace!("No dGPU tracked. Maybe booted with dgpu_disable=1 or gpu_mux_mode=0"); // info!("Is ASUS laptop, dgpu_disable = {disabled}"); if disabled { return Ok(GfxPower::AsusDisabled); } } } else if asus_gpu_mux_exists() { if let Ok(mode) = asus_gpu_mux_mode() { if mode == AsusGpuMuxMode::Discreet { return Ok(GfxPower::AsusMuxDiscreet); } } } Err(GfxError::NotSupported( "get_runtime_status: Could not find dGPU".to_string(), )) } pub fn set_runtime_pm(&self, pm: RuntimePowerManagement) -> Result<(), GfxError> { debug!("set_runtime_pm: pm = {:?}, {:?}", pm, self.devices); if self.devices.is_empty() { warn!("set_runtime_pm: Did not have dGPU handle"); return Ok(()); } if !matches!( self.vendor, GfxVendor::Unknown | GfxVendor::AsusDgpuDisabled ) { for dev in self.devices.iter() { dev.set_runtime_pm(pm)?; info!("set_runtime_pm: Set PM on {:?} to {pm:?}", dev.dev_path()); } return Ok(()); } if self.vendor == GfxVendor::AsusDgpuDisabled { info!("set_runtime_pm: ASUS dgpu_disable set, ignoring"); return Ok(()); } Err(GfxError::NotSupported( "set_runtime_pm: Could not find dGPU".to_string(), )) } pub fn set_hotplug(&self, state: HotplugState) -> Result<(), GfxError> { for dev in self.devices.iter() { if dev.is_dgpu() { dev.set_hotplug(state)?; break; } } Ok(()) } pub fn unbind(&self) -> Result<(), GfxError> { if self.vendor != GfxVendor::Unknown { for dev in self.devices.iter().rev() { dev.unbind()?; info!("Unbound {:?}", dev.dev_path()) } return Ok(()); } if self.vendor == GfxVendor::AsusDgpuDisabled { return Ok(()); } Err(GfxError::NotSupported( "unbind: Could not find dGPU".to_string(), )) } pub fn remove(&self) -> Result<(), GfxError> { if self.vendor != GfxVendor::Unknown { for dev in self.devices.iter().rev() { dev.remove()?; info!("Removed {:?}", dev.dev_path()) } return Ok(()); } Err(GfxError::NotSupported( "remove: Could not find dGPU".to_string(), )) } pub fn unbind_remove(&self) -> Result<(), GfxError> { self.unbind()?; self.remove() } pub fn do_driver_action(&self, action: DriverAction) -> Result<(), GfxError> { debug!( "do_driver_action: action = {}, {:?}", <&str>::from(action), self.devices ); if self.is_nvidia() { for driver in NVIDIA_DRIVERS.iter() { do_driver_action(driver, action)?; } } Ok(()) } } 0707010000001C000081A400000000000000000000000166FDDEC80000298B000000000000000000000000000000000000002600000000supergfxctl-5.2.4/src/special_asus.rsuse log::{debug, error, info, warn}; use std::{ fs::OpenOptions, io::{Read, Write}, path::Path, time::Duration, }; use tokio::time::sleep; use crate::{ error::GfxError, pci_device::{rescan_pci_bus, GfxMode}, }; const ASUS_DGPU_DISABLE_PATH: &str = "/sys/devices/platform/asus-nb-wmi/dgpu_disable"; const ASUS_EGPU_ENABLE_PATH: &str = "/sys/devices/platform/asus-nb-wmi/egpu_enable"; const ASUS_GPU_MUX_PATH: &str = "/sys/devices/platform/asus-nb-wmi/gpu_mux_mode"; const ASUS_EGPU_ALT_ENABLE_PATH: &str = "/sys/bus/platform/devices/asus-nb-wmi/egpu_enable"; pub const ASUS_MODULES_LOAD_PATH: &str = "/etc/modules-load.d/asus.conf"; pub const ASUS_MODULES_LOAD: &[u8] = br#" asus-wmi asus-nb-wmi "#; /// Create the config. Returns true if it already existed. pub fn create_asus_modules_load_conf() -> Result<bool, GfxError> { if Path::new(ASUS_MODULES_LOAD_PATH).exists() { info!("{} exists", ASUS_MODULES_LOAD_PATH); return Ok(true); } let mut file = std::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(ASUS_MODULES_LOAD_PATH) .map_err(|err| GfxError::Path(ASUS_MODULES_LOAD_PATH.into(), err))?; info!("Writing {}", ASUS_MODULES_LOAD_PATH); file.write_all(ASUS_MODULES_LOAD) .and_then(|_| file.sync_all()) .map_err(|err| GfxError::Write(ASUS_MODULES_LOAD_PATH.into(), err))?; Ok(false) } #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)] pub enum AsusGpuMuxMode { Discreet, Optimus, } impl From<i8> for AsusGpuMuxMode { fn from(v: i8) -> Self { if v != 0 { return Self::Optimus; } Self::Discreet } } impl From<char> for AsusGpuMuxMode { fn from(v: char) -> Self { if v != '0' { return Self::Optimus; } Self::Discreet } } pub fn asus_gpu_mux_exists() -> bool { Path::new(ASUS_GPU_MUX_PATH).exists() } pub fn asus_gpu_mux_mode() -> Result<AsusGpuMuxMode, GfxError> { let path = ASUS_GPU_MUX_PATH; let mut file = OpenOptions::new() .read(true) .open(path) .map_err(|err| GfxError::Path(path.into(), err))?; let mut data = Vec::new(); let res = file .read_to_end(&mut data) .map_err(|err| GfxError::Read(path.into(), err))?; if res == 0 { return Err(GfxError::Read( "Failed to read gpu_mux_mode".to_owned(), std::io::Error::new(std::io::ErrorKind::InvalidData, "Could not read"), )); } if let Some(d) = (data[0] as char).to_digit(10) { return Ok(AsusGpuMuxMode::from(d as i8)); } Err(GfxError::Read( "Failed to read gpu_mux_mode".to_owned(), std::io::Error::new(std::io::ErrorKind::InvalidData, "Could not read"), )) } pub fn asus_gpu_mux_set_igpu(igpu_on: bool) -> Result<(), GfxError> { debug!("asus_gpu_mux_set_igpu: {igpu_on}"); asus_gpu_toggle(igpu_on, ASUS_GPU_MUX_PATH)?; debug!("asus_gpu_mux_set_igpu: success"); Ok(()) } pub fn asus_dgpu_disable_exists() -> bool { if Path::new(ASUS_DGPU_DISABLE_PATH).exists() { return true; } false } pub fn asus_dgpu_disabled() -> Result<bool, GfxError> { let path = Path::new(ASUS_DGPU_DISABLE_PATH); let mut file = OpenOptions::new() .read(true) .open(path) .map_err(|err| GfxError::Path(ASUS_DGPU_DISABLE_PATH.to_string(), err))?; let mut buf = String::new(); file.read_to_string(&mut buf)?; if buf.contains('1') { return Ok(true); } Ok(false) } /// Special ASUS only feature. On toggle to `off` it will rescan the PCI bus. pub fn asus_dgpu_set_disabled(disabled: bool) -> Result<(), GfxError> { // Do not try to set it again if it has already been changed if asus_dgpu_disabled()? == disabled { debug!("asus_dgpu_set_disabled: already set to {disabled}. Early return"); return Ok(()); } debug!("asus_dgpu_set_disabled: {disabled}"); // There is a sleep here because this function is generally called after a hotplug // enable, and the deivces require at least a touch of time to finish powering up/down std::thread::sleep(Duration::from_millis(500)); // Need to set, scan, set to ensure mode is correctly set asus_gpu_toggle(disabled, ASUS_DGPU_DISABLE_PATH)?; if !disabled { // Purposefully blocking here. Need to force enough time for things to wake std::thread::sleep(Duration::from_millis(50)); rescan_pci_bus()?; } debug!("asus_dgpu_set_disabled: success"); Ok(()) } pub fn asus_egpu_enable_path() -> &'static str { if Path::new(ASUS_EGPU_ALT_ENABLE_PATH).exists() { return ASUS_EGPU_ALT_ENABLE_PATH; } return ASUS_EGPU_ENABLE_PATH; } pub fn asus_egpu_enable_exists() -> bool { if Path::new(ASUS_EGPU_ENABLE_PATH).exists() { return true; } if Path::new(ASUS_EGPU_ALT_ENABLE_PATH).exists() { return true; } false } pub fn asus_egpu_enabled() -> Result<bool, GfxError> { let path = Path::new(asus_egpu_enable_path()); let mut file = OpenOptions::new() .read(true) .open(path) .map_err(|err| GfxError::Path(asus_egpu_enable_path().to_string(), err))?; let mut buf = String::new(); file.read_to_string(&mut buf)?; if buf.contains('1') { return Ok(true); } Ok(false) } /// Special ASUS only feature. On toggle to `on` it will rescan the PCI bus. pub fn asus_egpu_set_enabled(enabled: bool) -> Result<(), GfxError> { if asus_egpu_enabled()? == enabled { // Do not try to set it again if it has already been changedif asus_egpu_enabled()? { return Ok(()); } debug!("asus_egpu_set_enabled: {enabled}"); // There is a sleep here because this function is generally called after a hotplug // enable, and the deivces require at least a touch of time to finish powering up std::thread::sleep(Duration::from_millis(500)); // Need to set, scan, set to ensure mode is correctly set asus_gpu_toggle(enabled, asus_egpu_enable_path())?; if enabled { // Purposefully blocking here. Need to force enough time for things to wake std::thread::sleep(Duration::from_millis(50)); rescan_pci_bus()?; } debug!("asus_egpu_set_enabled: success"); Ok(()) } fn asus_gpu_toggle(status: bool, path: &str) -> Result<(), GfxError> { let pathbuf = Path::new(path); let mut file = OpenOptions::new() .write(true) .open(pathbuf) .map_err(|err| GfxError::Path(path.to_string(), err))?; let status = if status { 1 } else { 0 }; file.write_all(status.to_string().as_bytes()) .map_err(|err| GfxError::Write(path.to_string(), err))?; debug!("switched {path} to {status}"); Ok(()) } /// To be called in main reload code. Specific actions required for asus laptops depending /// on is dgpu_disable, egpu_enable, or gpu_mux_mode are available. /// /// The returned mode may be different to the requested mode depending on the bios settings active, /// the differing value *must* be used. pub async fn asus_boot_safety_check( mode: GfxMode, asus_use_dgpu_disable: bool, ) -> Result<GfxMode, GfxError> { debug!("asus_reload: asus_use_dgpu_disable: {asus_use_dgpu_disable}"); // This is a bit of a crap cycle to ensure that dgpu_disable is there before setting it. if asus_use_dgpu_disable && !asus_dgpu_disable_exists() { if !create_asus_modules_load_conf()? { warn!( "asus_boot_safety_check: Reboot required due to {} creation", ASUS_MODULES_LOAD_PATH ); // let mut cmd = Command::new("reboot"); // cmd.spawn()?; } warn!("asus_boot_safety_check: HotPlug type Asus is set but asus-wmi appear not loaded yet. Trying for 2 seconds. If there are issues you may need to add asus_nb_wmi to modules.load.d"); let mut count = 2000 / 50; while !asus_dgpu_disable_exists() && count != 0 { sleep(Duration::from_millis(50)).await; count -= 1; } } if asus_gpu_mux_exists() { match asus_gpu_mux_mode()? { AsusGpuMuxMode::Discreet => { if asus_dgpu_disable_exists() && asus_dgpu_disabled()? { error!("asus_boot_safety_check: dgpu_disable is on while gpu_mux_mode is descrete, can't continue safely, attempting to set dgpu_disable off"); asus_dgpu_set_disabled(false)?; } else { info!("asus_boot_safety_check: dgpu_disable is off"); } return Ok(GfxMode::AsusMuxDgpu); } AsusGpuMuxMode::Optimus => { if mode == GfxMode::AsusMuxDgpu { warn!("asus_boot_safety_check: MUX is in Optimus mode but mode is set to AsusMuxDgpu. Switching to Hybrid"); return Ok(GfxMode::Hybrid); } } } } // Need to always check if dgpu_disable exists since GA401I series and older doesn't have this if asus_dgpu_disable_exists() { let dgpu_disabled = asus_dgpu_disabled()?; // If dgpu_disable is hard set then users won't have a dgpu at all, try set dgpu enabled if !asus_use_dgpu_disable && dgpu_disabled { warn!("It appears dgpu_disable is true on boot with HotPlug type not set to Asus, will attempt to re-enable dgpu"); if asus_dgpu_set_disabled(false) .map_err(|e| error!("asus_dgpu_set_disabled: {e:?}")) .is_ok() { return Ok(GfxMode::Hybrid); } else { return Ok(GfxMode::Integrated); } } else if dgpu_disabled && mode != GfxMode::Integrated { warn!("asus_boot_safety_check: dgpu_disable is on but the mode isn't Integrated, setting mode to Integrated"); return Ok(GfxMode::Integrated); } } if asus_egpu_enable_exists() { if asus_egpu_enabled()? && mode != GfxMode::AsusEgpu { warn!("asus_boot_safety_check: egpu_enable is on but the mode isn't AsusEgpu, setting mode to AsusEgpu"); return Ok(GfxMode::AsusEgpu); } else if asus_use_dgpu_disable // using asus hotplug? && asus_dgpu_disable_exists() && asus_dgpu_disabled()? // and dgpu is disabled? { return Ok(GfxMode::Integrated); // really should be in this mode if dgpu disabled } } Ok(mode) } 0707010000001D000081A400000000000000000000000166FDDEC800000A7E000000000000000000000000000000000000002100000000supergfxctl-5.2.4/src/systemd.rsuse crate::error::GfxError; use log::info; use std::process::Command; /// An action for `systemctl` #[derive(Debug, Copy, Clone)] pub enum SystemdUnitAction { Stop, Start, Restart, } impl From<SystemdUnitAction> for &str { fn from(s: SystemdUnitAction) -> Self { match s { SystemdUnitAction::Stop => "stop", SystemdUnitAction::Start => "start", SystemdUnitAction::Restart => "restart", } } } #[derive(Debug, Copy, Clone)] pub enum SystemdUnitState { Active, Inactive, } impl From<SystemdUnitState> for &str { fn from(s: SystemdUnitState) -> Self { match s { SystemdUnitState::Active => "active", SystemdUnitState::Inactive => "inactive", } } } /// Change the state of a systemd unit. Blocks while running command. pub fn do_systemd_unit_action(action: SystemdUnitAction, unit: &str) -> Result<(), GfxError> { let mut cmd = Command::new("systemctl"); cmd.arg(<&str>::from(action)); cmd.arg(unit); info!("Running systemctl command {action:?} on {unit}"); let status = cmd .status() .map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?; if !status.success() { let msg = format!("systemctl {action:?} {unit} failed: {status:?}",); return Err(GfxError::SystemdUnitAction(msg)); } Ok(()) } /// Get systemd unit state. Blocks while command is run. pub fn is_systemd_unit_state(state: SystemdUnitState, unit: &str) -> Result<bool, GfxError> { let mut cmd = Command::new("systemctl"); cmd.arg("is-active"); cmd.arg(unit); let output = cmd .output() .map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?; if output.stdout.starts_with(<&str>::from(state).as_bytes()) { return Ok(true); } Ok(false) } /// Wait for a systemd unit to change to `state`. Checks state every 250ms for 3 seconds. Blocks while running wait. pub fn wait_systemd_unit_state(state: SystemdUnitState, unit: &str) -> Result<(), GfxError> { let mut cmd = Command::new("systemctl"); cmd.arg("is-active"); cmd.arg(unit); let mut count = 0; while count <= (4 * 3) { // 3 seconds max let output = cmd .output() .map_err(|err| GfxError::Command(format!("{:?}", cmd), err))?; if output.stdout.starts_with(<&str>::from(state).as_bytes()) { return Ok(()); } // fine to block here, nobody doing shit now std::thread::sleep(std::time::Duration::from_millis(250)); count += 1; } Err(GfxError::SystemdUnitWaitTimeout(<&str>::from(state).into())) } 0707010000001E000041ED00000000000000000000000266FDDEC800000000000000000000000000000000000000000000001C00000000supergfxctl-5.2.4/src/tests0707010000001F000081A400000000000000000000000166FDDEC800005510000000000000000000000000000000000000002700000000supergfxctl-5.2.4/src/tests/actions.rsuse crate::{actions::StagedAction, error::GfxError}; impl StagedAction { /// Verification that the action lists are in the correct order. If incorrect then lockups and other errors can occur pub fn verify_previous_action_for_current( &self, previous_action: StagedAction, ) -> Result<(), GfxError> { if match self { StagedAction::StopDisplayManager => previous_action == StagedAction::WaitLogout, StagedAction::StartDisplayManager => true, StagedAction::NoLogind => [ StagedAction::None, StagedAction::NoLogind, StagedAction::HotplugUnplug, StagedAction::AsusDgpuDisable, StagedAction::AsusEgpuDisable, StagedAction::DevTreeManaged, StagedAction::EnableNvidiaPowerd, StagedAction::NotNvidia, ] .contains(&previous_action), StagedAction::LoadGpuDrivers => previous_action == StagedAction::RescanPci, StagedAction::UnloadGpuDrivers => [ StagedAction::StopDisplayManager, StagedAction::DisableNvidiaPowerd, StagedAction::KillNvidia, StagedAction::KillAmd, StagedAction::NotNvidia, StagedAction::AsusEgpuDisable, ] .contains(&previous_action), StagedAction::KillNvidia => [ StagedAction::StopDisplayManager, StagedAction::DisableNvidiaPowerd, StagedAction::None, ] .contains(&previous_action), StagedAction::KillAmd => [ StagedAction::NotNvidia, StagedAction::DisableNvidiaPowerd, StagedAction::StopDisplayManager, StagedAction::None, ] .contains(&previous_action), StagedAction::EnableNvidiaPowerd => [ StagedAction::DevTreeManaged, StagedAction::LoadGpuDrivers, StagedAction::None, ] .contains(&previous_action), StagedAction::DisableNvidiaPowerd => [ StagedAction::StopDisplayManager, StagedAction::NoLogind, StagedAction::RescanPci, StagedAction::None, ] .contains(&previous_action), StagedAction::LoadVfioDrivers => true, StagedAction::UnloadVfioDrivers => true, StagedAction::RescanPci => [ StagedAction::None, // Allow None due to VFIO StagedAction::AsusDgpuEnable, StagedAction::AsusDgpuDisable, StagedAction::AsusEgpuEnable, StagedAction::AsusEgpuDisable, StagedAction::HotplugPlug, StagedAction::HotplugUnplug, StagedAction::DevTreeManaged, StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, ] .contains(&previous_action), StagedAction::UnbindRemoveGpu => [ StagedAction::UnloadGpuDrivers, StagedAction::UnloadVfioDrivers, ] .contains(&previous_action), StagedAction::UnbindGpu => [ StagedAction::UnloadGpuDrivers, StagedAction::UnloadVfioDrivers, ] .contains(&previous_action), StagedAction::HotplugUnplug | StagedAction::HotplugPlug | StagedAction::AsusDgpuDisable | StagedAction::AsusDgpuEnable | StagedAction::AsusEgpuDisable | StagedAction::AsusEgpuEnable | StagedAction::DevTreeManaged => [ StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, ] .contains(&previous_action), StagedAction::AsusMuxIgpu => [ StagedAction::None, StagedAction::DisableNvidiaPowerd, StagedAction::NotNvidia, ] .contains(&previous_action), StagedAction::AsusMuxDgpu => [ StagedAction::EnableNvidiaPowerd, StagedAction::NotNvidia, StagedAction::None, ] .contains(&previous_action), StagedAction::WriteModprobeConf => [ StagedAction::StopDisplayManager, StagedAction::NoLogind, StagedAction::UnbindRemoveGpu, StagedAction::UnloadGpuDrivers, StagedAction::UnloadVfioDrivers, StagedAction::None, ] .contains(&previous_action), StagedAction::CheckVulkanIcd | StagedAction::WaitLogout | StagedAction::NotNvidia | StagedAction::None => true, } { Ok(()) } else { Err(GfxError::IncorrectActionOrder(*self, previous_action)) } } pub fn verify_next_allowed_action( &self, next_allowed_action: StagedAction, ) -> Result<(), GfxError> { if match self { StagedAction::WaitLogout => StagedAction::StopDisplayManager == next_allowed_action, StagedAction::StopDisplayManager => [ StagedAction::DisableNvidiaPowerd, StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, StagedAction::UnloadVfioDrivers, StagedAction::KillAmd, StagedAction::KillNvidia, StagedAction::NotNvidia, ] .contains(&next_allowed_action), StagedAction::StartDisplayManager => { [StagedAction::None].contains(&next_allowed_action) } StagedAction::NoLogind => [ StagedAction::NoLogind, StagedAction::NotNvidia, StagedAction::DisableNvidiaPowerd, StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, ] .contains(&next_allowed_action), StagedAction::LoadGpuDrivers => [ StagedAction::EnableNvidiaPowerd, StagedAction::NotNvidia, StagedAction::None, ] .contains(&next_allowed_action), StagedAction::UnloadGpuDrivers => [ StagedAction::UnbindGpu, StagedAction::UnbindRemoveGpu, StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, ] .contains(&next_allowed_action), StagedAction::KillNvidia => [ StagedAction::UnloadGpuDrivers, StagedAction::UnloadVfioDrivers, ] .contains(&next_allowed_action), StagedAction::KillAmd => [ StagedAction::UnloadGpuDrivers, StagedAction::UnloadVfioDrivers, ] .contains(&next_allowed_action), StagedAction::EnableNvidiaPowerd => [ StagedAction::StartDisplayManager, StagedAction::AsusMuxDgpu, StagedAction::NoLogind, StagedAction::None, ] .contains(&next_allowed_action), StagedAction::DisableNvidiaPowerd => { [StagedAction::KillNvidia, StagedAction::KillAmd].contains(&next_allowed_action) } StagedAction::LoadVfioDrivers => [StagedAction::None].contains(&next_allowed_action), StagedAction::UnloadVfioDrivers => [ StagedAction::UnbindRemoveGpu, StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, ] .contains(&next_allowed_action), StagedAction::DevTreeManaged => [ StagedAction::StartDisplayManager, StagedAction::NoLogind, StagedAction::RescanPci, ] .contains(&next_allowed_action), StagedAction::RescanPci => [ StagedAction::LoadGpuDrivers, StagedAction::DisableNvidiaPowerd, StagedAction::NotNvidia, ] .contains(&next_allowed_action), StagedAction::UnbindRemoveGpu => [ StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, ] .contains(&next_allowed_action), StagedAction::UnbindGpu => { [StagedAction::LoadVfioDrivers].contains(&next_allowed_action) } StagedAction::HotplugUnplug => { [StagedAction::StartDisplayManager, StagedAction::NoLogind] .contains(&next_allowed_action) } StagedAction::HotplugPlug => [StagedAction::RescanPci].contains(&next_allowed_action), StagedAction::AsusDgpuDisable => { [StagedAction::StartDisplayManager, StagedAction::NoLogind] .contains(&next_allowed_action) } StagedAction::AsusDgpuEnable => { [StagedAction::RescanPci].contains(&next_allowed_action) } StagedAction::AsusEgpuDisable => [].contains(&next_allowed_action), StagedAction::AsusEgpuEnable => { [StagedAction::RescanPci].contains(&next_allowed_action) } StagedAction::AsusMuxIgpu => [].contains(&next_allowed_action), StagedAction::AsusMuxDgpu => [].contains(&next_allowed_action), StagedAction::WriteModprobeConf => [ StagedAction::AsusEgpuDisable, StagedAction::AsusEgpuEnable, StagedAction::HotplugUnplug, StagedAction::AsusDgpuDisable, StagedAction::DevTreeManaged, StagedAction::HotplugPlug, StagedAction::AsusDgpuEnable, StagedAction::LoadVfioDrivers, StagedAction::RescanPci, StagedAction::CheckVulkanIcd, ] .contains(&next_allowed_action), StagedAction::NotNvidia => [ StagedAction::KillAmd, StagedAction::StartDisplayManager, StagedAction::NoLogind, ] .contains(&next_allowed_action), StagedAction::None => [ StagedAction::RescanPci, StagedAction::NoLogind, StagedAction::WriteModprobeConf, StagedAction::CheckVulkanIcd, StagedAction::WaitLogout, StagedAction::NotNvidia, StagedAction::KillNvidia, StagedAction::KillAmd, StagedAction::EnableNvidiaPowerd, StagedAction::DisableNvidiaPowerd, StagedAction::UnloadVfioDrivers, ] .contains(&next_allowed_action), StagedAction::CheckVulkanIcd => true, } { Ok(()) } else { Err(GfxError::IncorrectActionOrder(next_allowed_action, *self)) } } } #[cfg(test)] mod tests { use crate::{ actions::{Action, StagedAction}, config::GfxConfig, pci_device::{GfxMode, GfxVendor, HotplugType}, }; #[test] fn verify_hybrid_to_integrated_action_order() { let mut config = GfxConfig { config_path: Default::default(), mode: crate::pci_device::GfxMode::Hybrid, tmp_mode: None, pending_mode: None, pending_action: None, vfio_enable: false, vfio_save: false, always_reboot: false, no_logind: false, logout_timeout_s: 10, hotplug_type: crate::pci_device::HotplugType::None, }; let actions = StagedAction::action_list_for_switch( &config, GfxVendor::Nvidia, GfxMode::Hybrid, GfxMode::Integrated, ); match actions { Action::UserAction(_) => panic!("Should be a list of actions"), Action::StagedActions(actions) => { let mut previous_action = StagedAction::None; for action in actions { action .verify_previous_action_for_current(previous_action) .map_err(|e| { println!("Action thread errored: {e}"); }) .unwrap(); previous_action = action; } } } config.no_logind = true; let actions = StagedAction::action_list_for_switch( &config, GfxVendor::Nvidia, GfxMode::Hybrid, GfxMode::Integrated, ); match actions { Action::UserAction(_) => panic!("Should be a list of actions"), Action::StagedActions(actions) => { let mut previous_action = StagedAction::None; for action in actions { action .verify_previous_action_for_current(previous_action) .map_err(|e| { println!("Action thread errored: {e}"); }) .unwrap(); previous_action = action; } } } } #[test] fn verify_integrated_to_hybrid_action_order() { let mut config = GfxConfig { config_path: Default::default(), mode: crate::pci_device::GfxMode::Integrated, tmp_mode: None, pending_mode: None, pending_action: None, vfio_enable: false, vfio_save: false, always_reboot: false, no_logind: false, logout_timeout_s: 10, hotplug_type: crate::pci_device::HotplugType::None, }; let actions = StagedAction::action_list_for_switch( &config, GfxVendor::Nvidia, GfxMode::Integrated, GfxMode::Hybrid, ); match actions { Action::UserAction(_) => panic!("Should be a list of actions"), Action::StagedActions(actions) => { let mut previous_action = StagedAction::None; for action in actions { action .verify_previous_action_for_current(previous_action) .map_err(|e| { println!("Action thread errored: {e}"); }) .unwrap(); previous_action = action; } } } config.no_logind = true; let actions = StagedAction::action_list_for_switch( &config, GfxVendor::Nvidia, GfxMode::Integrated, GfxMode::Hybrid, ); match actions { Action::UserAction(_) => panic!("Should be a list of actions"), Action::StagedActions(actions) => { let mut previous_action = StagedAction::None; for action in actions { action .verify_previous_action_for_current(previous_action) .map_err(|e| { println!("Action thread errored: {e}"); }) .unwrap(); previous_action = action; } } } } #[test] fn verify_all_previous() { let modes = [ GfxMode::Hybrid, GfxMode::Integrated, GfxMode::NvidiaNoModeset, GfxMode::Vfio, GfxMode::AsusEgpu, GfxMode::AsusMuxDgpu, GfxMode::None, ]; let mut config = GfxConfig { config_path: Default::default(), mode: crate::pci_device::GfxMode::Hybrid, tmp_mode: None, pending_mode: None, pending_action: None, vfio_enable: false, vfio_save: false, always_reboot: false, no_logind: false, logout_timeout_s: 10, hotplug_type: crate::pci_device::HotplugType::None, }; let run = |config: &GfxConfig| { for from in modes { for to in modes { for vendor in [GfxVendor::Nvidia, GfxVendor::Amd] { if vendor == GfxVendor::Amd && from == GfxMode::NvidiaNoModeset || from == GfxMode::AsusEgpu || from == GfxMode::AsusMuxDgpu || to == GfxMode::NvidiaNoModeset || to == GfxMode::AsusEgpu || to == GfxMode::AsusMuxDgpu { continue; } let actions = StagedAction::action_list_for_switch(config, vendor, from, to); match actions { Action::UserAction(_) => {} //panic!("Should be a list of actions"), Action::StagedActions(actions) => { let mut previous_action = StagedAction::None; for action in actions { action .verify_previous_action_for_current(previous_action) .map_err(|e| { println!( "Action thread errored: from:{from}, to:{to}, {e}" ); }) .unwrap(); previous_action = action; } } } } } } }; run(&config); config.hotplug_type = HotplugType::Asus; run(&config); config.hotplug_type = HotplugType::Std; run(&config); config.no_logind = true; config.hotplug_type = HotplugType::None; run(&config); config.hotplug_type = HotplugType::Asus; run(&config); config.hotplug_type = HotplugType::Std; run(&config); } #[test] fn verify_all_next() { let modes = [ GfxMode::Hybrid, GfxMode::Integrated, GfxMode::NvidiaNoModeset, GfxMode::Vfio, GfxMode::AsusEgpu, GfxMode::AsusMuxDgpu, GfxMode::None, ]; let mut config = GfxConfig { config_path: Default::default(), mode: crate::pci_device::GfxMode::Hybrid, tmp_mode: None, pending_mode: None, pending_action: None, vfio_enable: false, vfio_save: false, always_reboot: false, no_logind: false, logout_timeout_s: 10, hotplug_type: crate::pci_device::HotplugType::None, }; let run = |config: &GfxConfig| { for from in modes { for to in modes { for vendor in [GfxVendor::Nvidia, GfxVendor::Amd] { if vendor == GfxVendor::Amd && from == GfxMode::NvidiaNoModeset || from == GfxMode::AsusEgpu || from == GfxMode::AsusMuxDgpu || to == GfxMode::NvidiaNoModeset || to == GfxMode::AsusEgpu || to == GfxMode::AsusMuxDgpu { continue; } let actions = StagedAction::action_list_for_switch(config, vendor, from, to); match actions { Action::UserAction(_) => {} //panic!("Should be a list of actions"), Action::StagedActions(actions) => { let mut previous_action = StagedAction::None; for action in actions { previous_action .verify_next_allowed_action(action) .map_err(|e| { println!( "Action thread errored: from:{from}, to:{to}, {e}" ); }) .unwrap(); previous_action = action; } } } } } } }; run(&config); config.hotplug_type = HotplugType::Asus; run(&config); config.hotplug_type = HotplugType::Std; run(&config); config.no_logind = true; config.hotplug_type = HotplugType::None; run(&config); config.hotplug_type = HotplugType::Asus; run(&config); config.hotplug_type = HotplugType::Std; run(&config); } } 07070100000020000081A400000000000000000000000166FDDEC800000018000000000000000000000000000000000000002300000000supergfxctl-5.2.4/src/tests/mod.rspub(crate) mod actions; 07070100000021000081A400000000000000000000000166FDDEC800002109000000000000000000000000000000000000002400000000supergfxctl-5.2.4/src/zbus_iface.rsuse ::zbus::interface; use log::{error, info, warn}; use zbus::zvariant::ObjectPath; use zbus::SignalContext; use crate::{ actions::UserActionRequired, config::GfxConfigDbus, pci_device::{GfxMode, GfxPower}, special_asus::{asus_gpu_mux_mode, AsusGpuMuxMode}, DBUS_IFACE_PATH, VERSION, }; use super::controller::CtrlGraphics; #[interface(name = "org.supergfxctl.Daemon")] impl CtrlGraphics { /// Get supergfxd version fn version(&self) -> zbus::fdo::Result<String> { Ok(VERSION.to_string()) } /// Get the current graphics mode: /// ```rust /// enum GfxMode { /// Hybrid, /// Integrated, /// NvidiaNoModeset, /// Vfio, /// AsusEgpu, /// AsusMuxDgpu, /// None, /// } /// # use supergfxctl::pci_device; /// # assert_eq!(pci_device::GfxMode::None as u8, 6); /// # assert_eq!(pci_device::GfxMode::Hybrid as u8, GfxMode::Hybrid as u8); /// # assert_eq!(pci_device::GfxMode::Integrated as u8, GfxMode::Integrated as u8); /// # assert_eq!(pci_device::GfxMode::NvidiaNoModeset as u8, GfxMode::NvidiaNoModeset as u8); /// # assert_eq!(pci_device::GfxMode::Vfio as u8, GfxMode::Vfio as u8); /// # assert_eq!(pci_device::GfxMode::AsusEgpu as u8, GfxMode::AsusEgpu as u8); /// # assert_eq!(pci_device::GfxMode::AsusMuxDgpu as u8, GfxMode::AsusMuxDgpu as u8); /// # assert_eq!(pci_device::GfxMode::None as u8, GfxMode::None as u8); /// ``` async fn mode(&self) -> zbus::fdo::Result<GfxMode> { if let Ok(state) = asus_gpu_mux_mode() { if state == AsusGpuMuxMode::Discreet { return Ok(GfxMode::AsusMuxDgpu); } } let config = self.config.lock().await; self.get_gfx_mode(&config).map_err(|err| { error!("{}", err); zbus::fdo::Error::Failed(format!("GFX fail: {}", err)) }) } /// Get list of supported modes async fn supported(&self) -> zbus::fdo::Result<Vec<GfxMode>> { if let Ok(state) = asus_gpu_mux_mode() { if state == AsusGpuMuxMode::Discreet { return Ok(vec![GfxMode::AsusMuxDgpu]); } } Ok(self.get_supported_modes().await) } /// Get the vendor name of the dGPU async fn vendor(&self) -> zbus::fdo::Result<String> { Ok(<&str>::from(self.get_gfx_vendor().await).to_string()) } /// Get the current power status: /// enum GfxPower { /// Active, /// Suspended, /// Off, /// AsusDisabled, /// Unknown, /// } async fn power(&self) -> zbus::fdo::Result<GfxPower> { if let Ok(state) = asus_gpu_mux_mode() { if state == AsusGpuMuxMode::Discreet { return Ok(GfxPower::AsusMuxDiscreet); } } let dgpu = self.dgpu.lock().await; dgpu.get_runtime_status().map_err(|err| { error!("{}", err); zbus::fdo::Error::Failed(format!("GFX fail: {}", err)) }) } /// Set the graphics mode: /// ```rust /// enum GfxMode { /// Hybrid, /// Integrated, /// NvidiaNoModeset, /// Vfio, /// AsusEgpu, /// AsusMuxDgpu, /// None, /// } /// # use supergfxctl::pci_device; /// # assert_eq!(pci_device::GfxMode::None as u8, 6); /// # assert_eq!(pci_device::GfxMode::Hybrid as u8, GfxMode::Hybrid as u8); /// # assert_eq!(pci_device::GfxMode::Integrated as u8, GfxMode::Integrated as u8); /// # assert_eq!(pci_device::GfxMode::NvidiaNoModeset as u8, GfxMode::NvidiaNoModeset as u8); /// # assert_eq!(pci_device::GfxMode::Vfio as u8, GfxMode::Vfio as u8); /// # assert_eq!(pci_device::GfxMode::AsusEgpu as u8, GfxMode::AsusEgpu as u8); /// # assert_eq!(pci_device::GfxMode::AsusMuxDgpu as u8, GfxMode::AsusMuxDgpu as u8); /// # assert_eq!(pci_device::GfxMode::None as u8, GfxMode::None as u8); /// ``` /// /// Returns action required: /// ```rust /// enum UserActionRequired { /// Logout, /// Reboot, /// SwitchToIntegrated, /// AsusEgpuDisable, /// Nothing, /// } /// # use supergfxctl::actions; /// # assert_eq!(actions::UserActionRequired::Nothing as u8, 4); /// # assert_eq!(actions::UserActionRequired::Logout as u8, UserActionRequired::Logout as u8); /// # assert_eq!(actions::UserActionRequired::Reboot as u8, UserActionRequired::Reboot as u8); /// # assert_eq!(actions::UserActionRequired::SwitchToIntegrated as u8, UserActionRequired::SwitchToIntegrated as u8); /// # assert_eq!(actions::UserActionRequired::AsusEgpuDisable as u8, UserActionRequired::AsusEgpuDisable as u8); /// # assert_eq!(actions::UserActionRequired::Nothing as u8, UserActionRequired::Nothing as u8); /// ``` async fn set_mode( &mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>, mode: GfxMode, ) -> zbus::fdo::Result<UserActionRequired> { info!("Switching gfx mode to {mode}"); let msg = self.set_gfx_mode(mode).await.map_err(|err| { error!("{}", err); zbus::fdo::Error::Failed(format!("GFX fail: {}", err)) })?; Self::notify_action(&ctxt, &msg) .await .unwrap_or_else(|err| warn!("{}", err)); Self::notify_gfx(&ctxt, &mode) .await .unwrap_or_else(|err| warn!("{}", err)); Ok(msg) } /// Get the `String` name of the pending mode change if any async fn pending_mode(&self) -> zbus::fdo::Result<GfxMode> { Ok(self.get_pending_mode().await) } /// Get the `String` name of the pending required user action if any async fn pending_user_action(&self) -> zbus::fdo::Result<UserActionRequired> { Ok(self.get_pending_user_action().await) } /// Get the base config, args in order are: /// pub mode: GfxMode, /// vfio_enable: bool, /// vfio_save: bool, /// compute_save: bool, /// always_reboot: bool, /// no_logind: bool, /// logout_timeout_s: u64, async fn config(&self) -> zbus::fdo::Result<GfxConfigDbus> { let cfg = self.config.lock().await; let cfg = GfxConfigDbus::from(&*cfg); Ok(cfg) } /// Set the base config, args in order are: /// pub mode: GfxMode, /// vfio_enable: bool, /// vfio_save: bool, /// compute_save: bool, /// always_reboot: bool, /// no_logind: bool, /// logout_timeout_s: u64, async fn set_config( &mut self, #[zbus(signal_context)] ctxt: SignalContext<'_>, config: GfxConfigDbus, ) -> zbus::fdo::Result<()> { let do_mode_change; let mode; { let mut cfg = self.config.lock().await; do_mode_change = cfg.mode == config.mode; mode = cfg.mode; cfg.vfio_enable = config.vfio_enable; cfg.vfio_save = config.vfio_save; cfg.always_reboot = config.always_reboot; cfg.no_logind = config.no_logind; cfg.logout_timeout_s = config.logout_timeout_s; } if do_mode_change { self.set_mode(ctxt, mode).await.ok(); } Ok(()) } /// Be notified when the dgpu status changes: /// enum GfxPower { /// Active, /// Suspended, /// Off, /// AsusDisabled, /// AsusMuxDiscreet, /// Unknown, /// } #[zbus(signal)] pub async fn notify_gfx_status( signal_ctxt: &SignalContext<'_>, status: &GfxPower, ) -> zbus::Result<()> { } /// Recieve a notification if the graphics mode changes and to which mode #[zbus(signal)] async fn notify_gfx(signal_ctxt: &SignalContext<'_>, vendor: &GfxMode) -> zbus::Result<()> {} /// Recieve a notification on required action if mode changes #[zbus(signal)] async fn notify_action( signal_ctxt: &SignalContext<'_>, action: &UserActionRequired, ) -> zbus::Result<()> { } } impl CtrlGraphics { pub async fn add_to_server(self, server: &mut zbus::ObjectServer) { server .at(&ObjectPath::from_str_unchecked(DBUS_IFACE_PATH), self) .await .map_err(|err| { warn!("CtrlGraphics: add_to_server {}", err); err }) .ok(); } } 07070100000022000081A400000000000000000000000166FDDEC800000BFB000000000000000000000000000000000000002400000000supergfxctl-5.2.4/src/zbus_proxy.rs#![allow(clippy::type_complexity)] //! # DBus interface proxy for: `org.supergfxctl.Daemon` //! //! This code was generated by `zbus-xmlgen` `3.0.0` from DBus introspection data. //! Source: `Interface '/org/supergfxctl/Gfx' from service 'org.supergfxctl.Daemon' on system bus`. //! //! You may prefer to adapt it, instead of using it verbatim. //! //! More information can be found in the //! [Writing a client proxy](https://dbus.pages.freedesktop.org/zbus/client.html) //! section of the zbus documentation. //! //! This DBus object implements //! [standard DBus interfaces](https://dbus.freedesktop.org/doc/dbus-specification.html), //! (`org.freedesktop.DBus.*`) for which the following zbus proxies can be used: //! //! * [`zbus::fdo::IntrospectableProxy`] //! * [`zbus::fdo::PropertiesProxy`] //! * [`zbus::fdo::PeerProxy`] //! //! …consequently `zbus-xmlgen` did not generate code for the above interfaces. use zbus::proxy; use crate::{ actions::UserActionRequired, pci_device::{GfxMode, GfxPower}, }; #[proxy( interface = "org.supergfxctl.Daemon", default_service = "org.supergfxctl.Daemon", default_path = "/org/supergfxctl/Gfx" )] trait Daemon { /// Version method fn version(&self) -> zbus::Result<String>; /// Get the base config, args in order are: /// pub mode: GfxMode, /// vfio_enable: bool, /// vfio_save: bool, /// compute_save: bool, /// always_reboot: bool, /// no_logind: bool, /// logout_timeout_s: u64, fn config(&self) -> zbus::Result<(u32, bool, bool, bool, bool, bool, u64, bool)>; /// Set the base config, args in order are: /// pub mode: GfxMode, /// vfio_enable: bool, /// vfio_save: bool, /// compute_save: bool, /// always_reboot: bool, /// no_logind: bool, /// logout_timeout_s: u64, fn set_config( &self, config: &(u32, bool, bool, bool, bool, bool, u64, bool), ) -> zbus::Result<()>; /// Get the current power status fn power(&self) -> zbus::Result<GfxPower>; /// Set the graphics mode. Returns action required. fn set_mode(&self, mode: &GfxMode) -> zbus::Result<UserActionRequired>; /// Get the `String` name of the pending mode change if any fn pending_mode(&self) -> zbus::Result<GfxMode>; /// Get the `String` name of the pending required user action if any fn pending_user_action(&self) -> zbus::Result<UserActionRequired>; /// Get the current graphics mode fn mode(&self) -> zbus::Result<GfxMode>; /// Get list of supported modes fn supported(&self) -> zbus::Result<Vec<GfxMode>>; /// Get the vendor name of the dGPU fn vendor(&self) -> zbus::Result<String>; /// Be notified when the dgpu status changes #[zbus(signal)] fn notify_gfx_status(&self, status: GfxPower) -> zbus::Result<()>; /// NotifyAction signal #[zbus(signal)] fn notify_action(&self, action: UserActionRequired) -> zbus::Result<()>; /// NotifyGfx signal #[zbus(signal)] fn notify_gfx(&self, mode: GfxMode) -> zbus::Result<()>; } 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!438 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor