├── .envrc ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── .gitignore ├── LICENSE ├── README.md ├── envycontrol.py ├── flake.nix ├── logos ├── dark.png └── light.png └── setup.py /.envrc: -------------------------------------------------------------------------------- 1 | use flake -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a problem you have encountered while using EnvyControl 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Run `sudo envycontrol -s nvidia` 16 | 2. See error 17 | 18 | **Expected behavior** 19 | A clear and concise description of what you expected to happen. 20 | 21 | **Screenshots** 22 | If applicable, add screenshots to help explain your problem. 23 | 24 | **System Information:** 25 | - Model: [e.g. Acer Predator G3-571] 26 | - Distro: [e.g. Ubuntu 22.04.2 LTS] 27 | - Kernel: [e.g. 5.19.0-32-generic] 28 | - DE/WM and Display Manager (if applicable): [e.g. Gnome 42 with GDM] 29 | - EnvyControl version: [e.g. 3.0.0] 30 | - Nvidia driver version: [e.g. 530.30.02] 31 | - lspci output: 32 | ``` 33 | paste here 34 | ``` 35 | 36 | **Additional context** 37 | Add any other context about the problem here. If possible try to reproduce the problem with `--verbose` flag and attach its output. 38 | 39 | ``` 40 | paste here 41 | ``` 42 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Submit your suggestions for new features or improvements 4 | title: "[REQUEST]" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: I have a question related to EnvyControl 4 | title: "[QUESTION]" 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What is your question?** 11 | [...] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | .idea/ 3 | result/ 4 | .direnv/ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Victor Bayas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | EnvyControl Logo 5 | 6 |
7 | Optimus made easy 8 |
9 |
10 | 11 | # 👁‍🗨 EnvyControl 12 | 13 | EnvyControl is a CLI tool that provides an easy way to switch between GPU modes on Nvidia Optimus systems (i.e laptops with hybrid Intel + Nvidia or AMD + Nvidia graphics configurations) under Linux. 14 | 15 | ### 📖 License 16 | 17 | EnvyControl is free and open-source software released under the [MIT](https://github.com/bayasdev/envycontrol/blob/main/LICENSE) license. 18 | 19 | ### ⚠️ Disclaimer 20 | 21 | **This software is provided 'as-is' without any express or implied warranty.** 22 | 23 | Keep in mind any custom X.org configuration may get deleted or overwritten when switching modes. 24 | 25 | ## ✨ Features 26 | 27 | - 🐍 Written in Python 3+ for portability and compatibility 28 | - 🐧 Works across all major Linux distros ([tested distros](https://github.com/bayasdev/envycontrol/wiki/Frequently-Asked-Questions#tested-distros)) 29 | - 🖥️ Supports GDM, SDDM and LightDM display managers ([manual setup instructions](https://github.com/bayasdev/envycontrol/wiki/Frequently-Asked-Questions#what-to-do-if-my-display-manager-is-not-supported) also available) 30 | - 🔋 Save battery with integrated graphics mode 31 | - 💻 PCI-Express Runtime D3 (RTD3) Power Management support for Turing and later 32 | - 🎮 Coolbits support for GPU overclocking 33 | - 🔥 Fix screen tearing with ForceCompositionPipeline 34 | 35 | ## 📖 Graphics modes 36 | 37 | ### Integrated 38 | 39 | - The integrated Intel or AMD iGPU is used exclusively 40 | - Nvidia dGPU is turned off to reduce power consumption 41 | - External screens cannot be used if the video ports are wired to the dGPU 42 | 43 | ### Hybrid 44 | 45 | - Enables PRIME render offloading 46 | - RTD3 allows the dGPU to be dynamically turned off when not in use 47 | - Available choices for the `--rtd3` flag (based on the [official documentation](http://us.download.nvidia.com/XFree86/Linux-x86_64/530.30.02/README/dynamicpowermanagement.html)) 48 | - `0` disabled 49 | - `1` coarse-grained 50 | - `2` fine-grained (default value if you don't provide one) 51 | - `3` fine-grained for Ampere and later 52 | - Only works in Turing and later 53 | - Performance on external screens might be reduced 54 | 55 | ### Nvidia 56 | 57 | - The Nvidia dGPU is used exclusively 58 | - Higher graphical performance and higher power consumption 59 | - Recommended when working with external screens 60 | - If facing screen tearing enable ForceCompositionPipeline with the `--force-comp` flag 61 | - Allows overlocking (not recommended) with the `--coolbits` flag 62 | - The default value is `28` bits however it can be manually adjusted according to this [guide](https://wiki.archlinux.org/title/NVIDIA/Tips_and_tricks#Overclocking_and_cooling) 63 | - Wayland sessions default to hybrid mode 64 | 65 | ## ⚡️ Usage 66 | 67 | ``` 68 | usage: envycontrol.py [-h] [-v] [-q] [-s MODE] [--dm DISPLAY_MANAGER] [--force-comp] [--coolbits [VALUE]] [--rtd3 [VALUE]] [--reset-sddm] [--reset] [--verbose] 69 | 70 | options: 71 | -h, --help show this help message and exit 72 | -v, --version Output the current version 73 | -q, --query Query the current graphics mode 74 | -s MODE, --switch MODE 75 | Switch the graphics mode. Available choices: integrated, hybrid, nvidia 76 | --dm DISPLAY_MANAGER Manually specify your Display Manager for Nvidia mode. Available choices: gdm, gdm3, sddm, lightdm 77 | --force-comp Enable ForceCompositionPipeline on Nvidia mode 78 | --coolbits [VALUE] Enable Coolbits on Nvidia mode. Default if specified: 28 79 | --rtd3 [VALUE] Setup PCI-Express Runtime D3 (RTD3) Power Management on Hybrid mode. Available choices: 0, 1, 2, 3. Default if specified: 2 80 | --use-nvidia-current Use nvidia-current instead of nvidia for kernel modules 81 | --reset-sddm Restore default Xsetup file 82 | --reset Revert changes made by EnvyControl 83 | --cache-create Create cache used by EnvyControl; only works in hybrid mode 84 | --cache-delete Delete cache created by EnvyControl 85 | --cache-query Show cache created by EnvyControl 86 | --verbose Enable verbose mode 87 | ``` 88 | 89 | ### Some examples 90 | 91 | Set graphics mode to integrated: 92 | 93 | ``` 94 | sudo envycontrol -s integrated 95 | ``` 96 | 97 | Set graphics mode to hybrid and enable fine-grained power control: 98 | 99 | ``` 100 | sudo envycontrol -s hybrid --rtd3 101 | ``` 102 | 103 | Set graphics mode to nvidia, enable ForceCompositionPipeline and Coolbits with a value of 24: 104 | 105 | ``` 106 | sudo envycontrol -s nvidia --force-comp --coolbits 24 107 | ``` 108 | 109 | Set current graphics mode to nvidia and specify to setup LightDM display manager 110 | 111 | ``` 112 | sudo envycontrol -s nvidia --dm lightdm 113 | ``` 114 | 115 | Query the current graphics mode: 116 | 117 | ``` 118 | envycontrol --query 119 | ``` 120 | 121 | Revert all changes made by EnvyControl: 122 | 123 | ``` 124 | sudo envycontrol --reset 125 | ``` 126 | 127 | ### Caching added with 3.4.0 128 | A cache was added in version 3.4.0. The main purpose is to cache the Nvidia PCI bus ID so that a transition from integrated mode directly to nvidia mode is possible. A reboot is required as usual so the changes can take effect. 129 | 130 | #### Cache file location 131 | 132 | Note that these are just helpers to accomodate maintenance tasks. The cache is created automatically whenever switching away from hybrid mode - to integrated or nvidia mode. 133 | 134 | ```python 135 | CACHE_FILE_PATH = '/var/cache/envycontrol/cache.json' 136 | ``` 137 | 138 | #### File format 139 | 140 | ```json 141 | { 142 | "nvidia_gpu_pci_bus": "PCI:1:0:0" 143 | } 144 | ``` 145 | 146 | The cache is automatically re-created whenever a switch from hybrid mode is performed. 147 | 148 | #### Caching command line examples 149 | 150 | Create cache used by EnvyControl; only works in hybrid mode 151 | 152 | ``` 153 | sudo envycontrol --cache-create 154 | ``` 155 | 156 | When create cache is called when the system is in integrated or nvidia modes 157 | 158 | ``` 159 | sudo envycontrol --cache-create 160 | ... 161 | ValueError: --cache-create requires that the system be in the hybrid Optimus mode 162 | ``` 163 | 164 | 165 | Delete cache created by EnvyControl 166 | 167 | ``` 168 | sudo envycontrol --cache-delete 169 | ``` 170 | 171 | Show cache created by EnvyControl 172 | 173 | ``` 174 | sudo envycontrol --cache-query 175 | ``` 176 | 177 | 178 | ## ⬇️ Getting EnvyControl 179 | 180 | ### Arch Linux ([AUR](https://aur.archlinux.org/packages/envycontrol)) 181 | 182 | 1. `yay -S envycontrol` 183 | 2. Run `sudo envycontrol -s ` to switch graphics modes 184 | 185 | ### Fedora 186 | 187 | Use the [COPR](https://copr.fedorainfracloud.org/coprs/sunwire/envycontrol/) maintained by [@sunwire](https://github.com/sunwire) 188 | 189 | 1. Enable the repository with `sudo dnf copr enable sunwire/envycontrol` 190 | 2. `sudo dnf install python3-envycontrol` 191 | 3. Run `sudo envycontrol -s ` to switch graphics modes 192 | 193 | ### Enterprise Linux + EPEL 9 (RHEL 9, Rocky Linux 9, CentOS Stream 9, Alma Linux 9 etc.) 194 | 195 | Use the [COPR](https://copr.fedorainfracloud.org/coprs/thonkdifferent/envycontrol/) maintained by [@thonkdifferent](https://github.com/thonkdifferent) 196 | 197 | 1. Enable the repository with `sudo dnf copr enable thonkdifferent/envycontrol` 198 | 2. `sudo dnf install python3-envycontrol` 199 | 3. Run `sudo envycontrol -s ` to switch graphics modes 200 | 201 | ### Ubuntu / Debian 202 | 203 | Since [PEP668 adoption](https://www.linuxuprising.com/2023/03/next-debianubuntu-releases-will-likely.html) is no longer possible to install pip packages outside a virtual environment, instead use the provided deb package: 204 | 205 | 1. Go to the [latest release page](https://github.com/bayasdev/envycontrol/releases/latest) 206 | 2. Download the attached `python3-envycontrol_version.deb` package 207 | 3. Install it with `sudo apt -y install ./python3-envycontrol_version.deb` 208 | 4. Run `sudo envycontrol -s ` to switch graphics modes 209 | 210 | ### Nixos 211 | 212 | If you're using Nix Flakes: 213 | 214 | - Script could be executed using this command: 215 | 216 | ```sh 217 | nix run github:bayasdev/envycontrol -- 218 | ``` 219 | 220 | - For system-wide installation, add this flake to inputs in your configuration: 221 | 222 | ```sh 223 | inputs = { 224 | # ... 225 | envycontrol.url = github:bayasdev/envycontrol 226 | }; 227 | ``` 228 | 229 | And mention it in the packages like this: 230 | 231 | ```sh 232 | envycontrol.packages.x86_64-linux.default 233 | ``` 234 | 235 | Thanks to [@ITesserakt](https://github.com/ITesserakt) for adding initial NixOS support! 236 | 237 | ### OSTree Distros (Silverblue, Kinoite, Bazzite, etc.) 238 | 239 | These distributions are also supported by the same COPR repo as Fedora Workstation. Use the [COPR](https://copr.fedorainfracloud.org/coprs/sunwire/envycontrol/) maintained by [@sunwire](https://github.com/sunwire). 240 | 241 | 1. Enable the COPR by downloading the `.repo` file from the COPR page, linked above. Put the `.repo` file in `/etc/yum.repos.d`. 242 | 2. Clean package cache with `rpm-ostree cleanup -m`. 243 | 3. Overlay the package with `rpm-ostree install python-envycontrol`. 244 | 4. Reboot to apply the overlay. 245 | 5. Use EnvyControl with `sudo envycontrol -s ` 246 | 247 | ### From source 248 | 249 | 1. Clone this repository with `git clone https://github.com/bayasdev/envycontrol.git` or download the latest tarball from the releases page 250 | 2. Run the script from the root of the repository like this `python ./envycontrol.py -s ` 251 | 252 | 💡 Replace `python` with `python3` on Ubuntu/Debian 253 | 254 | ### Install globally as a pip package 255 | 256 | - From the root of the cloned repository run `sudo pip install .` 257 | - Now you can run `sudo envycontrol -s ` from any directory to switch graphics modes. 258 | 259 | ## 👕 GUIs 260 | 261 | ### Gnome Extension 262 | 263 | The [GPU profile selector](https://github.com/LorenzoMorelli/GPU_profile_selector) extension provides a simple way to switch between graphics modes in a few clicks, you can get it from [here](https://extensions.gnome.org/extension/5009/gpu-profile-selector/). 264 | 265 | **Make sure to have EnvyControl installed globally!** 266 | 267 | ![gpu profile selector screenshot](https://github.com/LorenzoMorelli/GPU_profile_selector/raw/main/img/extension_screenshot.png) 268 | 269 | ### KDE Widget 270 | 271 | [Optimus GPU Switcher](https://github.com/enielrodriguez/optimus-gpu-switcher) allows you to change the GPU mode easily, plus its icon is dynamic and serves as an indicator of the current mode. 272 | 273 | ![Screenshot_20230703_153738](https://github.com/enielrodriguez/optimus-gpu-switcher/assets/31964610/ace0c67e-9428-49fd-895c-48a236727898) 274 | 275 | ## 💡 Tips 276 | 277 | ### Black screen on Debian with Nvidia mode? 278 | 279 | Try adding `xrandr --auto` to your `~/.xsessionrc`. See https://github.com/bayasdev/envycontrol/issues/173#issuecomment-2205292306, also check the [Wiki](https://github.com/bayasdev/envycontrol/wiki/Frequently-Asked-Questions#what-to-do-if-my-display-manager-is-not-supported) for an alternative solution if this didn't work. 280 | 281 | ### `nvidia` kernel module is named `nvidia-current` on Debian 282 | 283 | If you're running into this situation you can use the `--use-nvidia-current` flag to make EnvyControl use the correct module name. 284 | 285 | ### Wayland session is missing on Gnome 43+ 286 | 287 | GDM now requires `NVreg_PreserveVideoMemoryAllocations` kernel parameter which breaks sleep in nvidia and hybrid mode, as well as rtd3 in hybrid mode, so EnvyControl disables it, if you need a Wayland session follow the instructions below 288 | 289 | ``` 290 | sudo systemctl enable nvidia-{suspend,resume,hibernate} 291 | sudo ln -s /dev/null /etc/udev/rules.d/61-gdm.rules 292 | ``` 293 | 294 | ### The `/usr/share/sddm/scripts/Xsetup` file is missing on my system 295 | 296 | If this ever happens please run `sudo envycontrol --reset-sddm`. 297 | 298 | ### Files to remove if uninstalling `envycontrol` 299 | The below files are created by `envycontrol`, and you may want to remove them manually if they are not removed automatically to avoid any incorrect system behaviour. 300 | * `/var/cache/envycontrol` 301 | * `/etc/modprobe.d/blacklist-nvidia.conf` 302 | * `/lib/udev/rules.d/50-remove-nvidia.rules` 303 | * `/lib/udev/rules.d/80-nvidia-pm.rules` 304 | * `/etc/X11/xorg.conf` 305 | * `/etc/X11/xorg.conf.d/10-nvidia.conf` 306 | * `/etc/modprobe.d/nvidia.conf` 307 | 308 | ## ❓ Frequently Asked Questions (FAQ) 309 | 310 | [Read here](https://github.com/bayasdev/envycontrol/wiki/Frequently-Asked-Questions) 311 | 312 | ## 🐞 I have a problem 313 | 314 | Open an issue and **don't forget to complete all the requested fields!** 315 | 316 | ## ☕️ Buy me a coffee 317 | 318 | [PayPal](https://www.paypal.com/paypalme/bayasdev) 319 | -------------------------------------------------------------------------------- /envycontrol.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import logging 4 | import os 5 | import re 6 | import subprocess 7 | import sys 8 | from contextlib import contextmanager 9 | 10 | # begin constants definition 11 | 12 | VERSION = '3.5.2' 13 | 14 | # Note: Do NOT remove this in cleanup! 15 | CACHE_FILE_PATH = '/var/cache/envycontrol/cache.json' 16 | 17 | BLACKLIST_PATH = '/etc/modprobe.d/blacklist-nvidia.conf' 18 | 19 | BLACKLIST_CONTENT = '''# Automatically generated by EnvyControl 20 | 21 | blacklist nouveau 22 | blacklist nvidia 23 | blacklist nvidia_drm 24 | blacklist nvidia_uvm 25 | blacklist nvidia_modeset 26 | blacklist nvidia_current 27 | blacklist nvidia_current_drm 28 | blacklist nvidia_current_uvm 29 | blacklist nvidia_current_modeset 30 | blacklist i2c_nvidia_gpu 31 | alias nouveau off 32 | alias nvidia off 33 | alias nvidia_drm off 34 | alias nvidia_uvm off 35 | alias nvidia_modeset off 36 | alias nvidia_current off 37 | alias nvidia_current_drm off 38 | alias nvidia_current_uvm off 39 | alias nvidia_current_modeset off 40 | alias i2c_nvidia_gpu off 41 | ''' 42 | 43 | UDEV_INTEGRATED_PATH = '/etc/udev/rules.d/50-remove-nvidia.rules' 44 | 45 | UDEV_INTEGRATED = '''# Automatically generated by EnvyControl 46 | 47 | # Remove NVIDIA USB xHCI Host Controller devices, if present 48 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{power/control}="auto", ATTR{remove}="1" 49 | 50 | # Remove NVIDIA USB Type-C UCSI devices, if present 51 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c8000", ATTR{power/control}="auto", ATTR{remove}="1" 52 | 53 | # Remove NVIDIA Audio devices, if present 54 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{power/control}="auto", ATTR{remove}="1" 55 | 56 | # Remove NVIDIA VGA/3D controller devices 57 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x03[0-9]*", ATTR{power/control}="auto", ATTR{remove}="1" 58 | ''' 59 | 60 | UDEV_PM_PATH = '/etc/udev/rules.d/80-nvidia-pm.rules' 61 | 62 | UDEV_PM_CONTENT = '''# Automatically generated by EnvyControl 63 | 64 | # Remove NVIDIA USB xHCI Host Controller devices, if present 65 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c0330", ATTR{remove}="1" 66 | 67 | # Remove NVIDIA USB Type-C UCSI devices, if present 68 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x0c8000", ATTR{remove}="1" 69 | 70 | # Remove NVIDIA Audio devices, if present 71 | ACTION=="add", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x040300", ATTR{remove}="1" 72 | 73 | # Enable runtime PM for NVIDIA VGA/3D controller devices on driver bind 74 | ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="auto" 75 | ACTION=="bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="auto" 76 | 77 | # Disable runtime PM for NVIDIA VGA/3D controller devices on driver unbind 78 | ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030000", TEST=="power/control", ATTR{power/control}="on" 79 | ACTION=="unbind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", TEST=="power/control", ATTR{power/control}="on" 80 | ''' 81 | 82 | XORG_PATH = '/etc/X11/xorg.conf' 83 | 84 | XORG_INTEL = '''# Automatically generated by EnvyControl 85 | 86 | Section "ServerLayout" 87 | Identifier "layout" 88 | Screen 0 "nvidia" 89 | Inactive "intel" 90 | EndSection 91 | 92 | Section "Device" 93 | Identifier "nvidia" 94 | Driver "nvidia" 95 | BusID "{}" 96 | EndSection 97 | 98 | Section "Screen" 99 | Identifier "nvidia" 100 | Device "nvidia" 101 | Option "AllowEmptyInitialConfiguration" 102 | EndSection 103 | 104 | Section "Device" 105 | Identifier "intel" 106 | Driver "modesetting" 107 | EndSection 108 | 109 | Section "Screen" 110 | Identifier "intel" 111 | Device "intel" 112 | EndSection 113 | ''' 114 | 115 | XORG_AMD = '''# Automatically generated by EnvyControl 116 | 117 | Section "ServerLayout" 118 | Identifier "layout" 119 | Screen 0 "nvidia" 120 | Inactive "amdgpu" 121 | EndSection 122 | 123 | Section "Device" 124 | Identifier "nvidia" 125 | Driver "nvidia" 126 | BusID "{}" 127 | EndSection 128 | 129 | Section "Screen" 130 | Identifier "nvidia" 131 | Device "nvidia" 132 | Option "AllowEmptyInitialConfiguration" 133 | EndSection 134 | 135 | Section "Device" 136 | Identifier "amdgpu" 137 | Driver "amdgpu" 138 | EndSection 139 | 140 | Section "Screen" 141 | Identifier "amd" 142 | Device "amdgpu" 143 | EndSection 144 | ''' 145 | 146 | EXTRA_XORG_PATH = '/etc/X11/xorg.conf.d/10-nvidia.conf' 147 | 148 | EXTRA_XORG_CONTENT = '''# Automatically generated by EnvyControl 149 | 150 | Section "OutputClass" 151 | Identifier "nvidia" 152 | MatchDriver "nvidia-drm" 153 | Driver "nvidia" 154 | ''' 155 | 156 | FORCE_COMP = ' Option "ForceCompositionPipeline" "true"\n' 157 | 158 | COOLBITS = ' Option "Coolbits" "{}"\n' 159 | 160 | MODESET_PATH = '/etc/modprobe.d/nvidia.conf' 161 | 162 | MODESET_CONTENT = '''# Automatically generated by EnvyControl 163 | 164 | options nvidia-drm modeset=1 165 | options nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0 166 | ''' 167 | 168 | MODESET_CURRENT_CONTENT = '''# Automatically generated by EnvyControl 169 | 170 | options nvidia-current-drm modeset=1 171 | options nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0 172 | ''' 173 | 174 | MODESET_RTD3 = '''# Automatically generated by EnvyControl 175 | 176 | options nvidia-drm modeset=1 177 | options nvidia "NVreg_DynamicPowerManagement=0x0{}" 178 | options nvidia NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0 179 | ''' 180 | 181 | MODESET_CURRENT_RTD3 = '''# Automatically generated by EnvyControl 182 | 183 | options nvidia-current-drm modeset=1 184 | options nvidia-current "NVreg_DynamicPowerManagement=0x0{}" 185 | options nvidia-current NVreg_UsePageAttributeTable=1 NVreg_InitializeSystemMemoryAllocations=0 186 | ''' 187 | 188 | SDDM_XSETUP_PATH = '/usr/share/sddm/scripts/Xsetup' 189 | 190 | SDDM_XSETUP_CONTENT = '''#!/bin/sh 191 | # Xsetup - run as root before the login dialog appears 192 | 193 | ''' 194 | 195 | LIGHTDM_SCRIPT_PATH = '/etc/lightdm/nvidia.sh' 196 | 197 | LIGHTDM_CONFIG_PATH = '/etc/lightdm/lightdm.conf.d/20-nvidia.conf' 198 | 199 | LIGHTDM_CONFIG_CONTENT = '''# Automatically generated by EnvyControl 200 | 201 | [Seat:*] 202 | display-setup-script=/etc/lightdm/nvidia.sh 203 | ''' 204 | 205 | NVIDIA_XRANDR_SCRIPT = '''#!/bin/sh 206 | # Automatically generated by EnvyControl 207 | 208 | current="" 209 | 210 | xrandr --setprovideroutputsource "{}" NVIDIA-0 211 | xrandr --auto 212 | 213 | for next in $(xrandr --listmonitors | grep -E " *[0-9]+:.*" | cut -d" " -f6); do 214 | [ -z "$current" ] && current=$next && continue 215 | xrandr --output "$current" --auto --output "$next" --auto --right-of "$current" 216 | current=$next 217 | done 218 | ''' 219 | 220 | SUPPORTED_MODES = ['integrated', 'hybrid', 'nvidia'] 221 | SUPPORTED_DISPLAY_MANAGERS = ['gdm', 'gdm3', 'sddm', 'lightdm'] 222 | RTD3_MODES = [0, 1, 2, 3] 223 | 224 | # end constants definition 225 | 226 | 227 | def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_comp, coolbits_value, rtd3_value, use_nvidia_current): 228 | print(f"Switching to {graphics_mode} mode") 229 | 230 | if graphics_mode == 'integrated': 231 | 232 | if logging.getLogger().level == logging.DEBUG: 233 | service = subprocess.run( 234 | ["systemctl", "disable", "nvidia-persistenced.service"]) 235 | else: 236 | service = subprocess.run( 237 | ["systemctl", "disable", "nvidia-persistenced.service"], 238 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 239 | if service.returncode == 0: 240 | print('Successfully disabled nvidia-persistenced.service') 241 | else: 242 | logging.error("An error ocurred while disabling service") 243 | 244 | cleanup() 245 | 246 | # blacklist all nouveau and Nvidia modules 247 | create_file(BLACKLIST_PATH, BLACKLIST_CONTENT) 248 | 249 | # power off the Nvidia GPU with udev rules 250 | create_file(UDEV_INTEGRATED_PATH, UDEV_INTEGRATED) 251 | 252 | rebuild_initramfs() 253 | elif graphics_mode == 'hybrid': 254 | print( 255 | f"Enable PCI-Express Runtime D3 (RTD3) Power Management: {rtd3_value or False}") 256 | cleanup() 257 | 258 | if logging.getLogger().level == logging.DEBUG: 259 | service = subprocess.run( 260 | ["systemctl", "enable", "nvidia-persistenced.service"]) 261 | else: 262 | service = subprocess.run( 263 | ["systemctl", "enable", "nvidia-persistenced.service"], 264 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 265 | if service.returncode == 0: 266 | print('Successfully enabled nvidia-persistenced.service') 267 | else: 268 | logging.error("An error ocurred while enabling service") 269 | 270 | if rtd3_value == None: 271 | if use_nvidia_current: 272 | create_file(MODESET_PATH, MODESET_CURRENT_CONTENT) 273 | else: 274 | create_file(MODESET_PATH, MODESET_CONTENT) 275 | else: 276 | # setup rtd3 277 | if use_nvidia_current: 278 | create_file( 279 | MODESET_PATH, MODESET_CURRENT_RTD3.format(rtd3_value)) 280 | else: 281 | create_file(MODESET_PATH, MODESET_RTD3.format(rtd3_value)) 282 | create_file(UDEV_PM_PATH, UDEV_PM_CONTENT) 283 | 284 | rebuild_initramfs() 285 | elif graphics_mode == 'nvidia': 286 | print(f"Enable ForceCompositionPipeline: {enable_force_comp}") 287 | print(f"Enable Coolbits: {coolbits_value or False}") 288 | 289 | if logging.getLogger().level == logging.DEBUG: 290 | service = subprocess.run( 291 | ["systemctl", "enable", "nvidia-persistenced.service"]) 292 | else: 293 | service = subprocess.run( 294 | ["systemctl", "enable", "nvidia-persistenced.service"], 295 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 296 | if service.returncode == 0: 297 | print('Successfully enabled nvidia-persistenced.service') 298 | else: 299 | logging.error("An error ocurred while enabling service") 300 | 301 | cleanup() 302 | # get the Nvidia dGPU PCI bus 303 | nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus() 304 | 305 | # get iGPU vendor 306 | igpu_vendor = get_igpu_vendor() 307 | 308 | # create the X.org config 309 | if igpu_vendor == 'intel': 310 | create_file(XORG_PATH, XORG_INTEL.format(nvidia_gpu_pci_bus)) 311 | elif igpu_vendor == 'amd': 312 | create_file(XORG_PATH, XORG_AMD.format(nvidia_gpu_pci_bus)) 313 | 314 | # enable modeset for Nvidia driver 315 | if use_nvidia_current: 316 | create_file(MODESET_PATH, MODESET_CURRENT_CONTENT) 317 | else: 318 | create_file(MODESET_PATH, MODESET_CONTENT) 319 | 320 | # extra Xorg config 321 | if enable_force_comp and coolbits_value != None: 322 | create_file(EXTRA_XORG_PATH, EXTRA_XORG_CONTENT + FORCE_COMP + 323 | COOLBITS.format(coolbits_value) + 'EndSection\n') 324 | elif enable_force_comp: 325 | create_file(EXTRA_XORG_PATH, EXTRA_XORG_CONTENT + 326 | FORCE_COMP + 'EndSection\n') 327 | elif coolbits_value != None: 328 | create_file(EXTRA_XORG_PATH, EXTRA_XORG_CONTENT + 329 | COOLBITS.format(coolbits_value) + 'EndSection\n') 330 | 331 | # try to detect the display manager if not provided 332 | if user_display_manager == None: 333 | display_manager = get_display_manager() 334 | else: 335 | display_manager = user_display_manager 336 | 337 | # only sddm and lightdm require further config 338 | if display_manager == 'sddm': 339 | # backup Xsetup 340 | if os.path.exists(SDDM_XSETUP_PATH): 341 | logging.info("Creating Xsetup backup") 342 | with open(SDDM_XSETUP_PATH, mode='r', encoding='utf-8') as f: 343 | create_file(SDDM_XSETUP_PATH+'.bak', f.read()) 344 | create_file(SDDM_XSETUP_PATH, 345 | generate_xrandr_script(igpu_vendor), True) 346 | elif display_manager == 'lightdm': 347 | create_file(LIGHTDM_SCRIPT_PATH, 348 | generate_xrandr_script(igpu_vendor), True) 349 | create_file(LIGHTDM_CONFIG_PATH, LIGHTDM_CONFIG_CONTENT) 350 | 351 | rebuild_initramfs() 352 | print('Operation completed successfully') 353 | print('Please reboot your computer for changes to take effect!') 354 | 355 | 356 | def cleanup(): 357 | # define list of files to remove 358 | to_remove = [ 359 | BLACKLIST_PATH, 360 | UDEV_INTEGRATED_PATH, 361 | UDEV_PM_PATH, 362 | XORG_PATH, 363 | EXTRA_XORG_PATH, 364 | MODESET_PATH, 365 | LIGHTDM_SCRIPT_PATH, 366 | LIGHTDM_CONFIG_PATH, 367 | # legacy files 368 | '/etc/X11/xorg.conf.d/90-nvidia.conf', 369 | '/lib/udev/rules.d/50-remove-nvidia.rules', 370 | '/lib/udev/rules.d/80-nvidia-pm.rules' 371 | ] 372 | 373 | # remove each file in the list 374 | for file_path in to_remove: 375 | try: 376 | if os.path.exists(file_path): 377 | os.remove(file_path) 378 | logging.info(f"Removed file {file_path}") 379 | except OSError as e: 380 | # only warn if file exists (code 2) 381 | if e.errno != 2: 382 | logging.error(f"Failed to remove file '{file_path}': {e}") 383 | 384 | # restore Xsetup backup if found 385 | backup_path = SDDM_XSETUP_PATH + ".bak" 386 | if os.path.exists(backup_path): 387 | logging.info("Restoring Xsetup backup") 388 | with open(backup_path, mode="r", encoding="utf-8") as f: 389 | create_file(SDDM_XSETUP_PATH, f.read()) 390 | # remove backup 391 | os.remove(backup_path) 392 | logging.info(f"Removed file {backup_path}") 393 | 394 | 395 | def get_nvidia_gpu_pci_bus(): 396 | lspci_output = subprocess.check_output(['lspci']).decode('utf-8') 397 | for line in lspci_output.splitlines(): 398 | if 'NVIDIA' in line and ('VGA compatible controller' in line or '3D controller' in line): 399 | # remove leading zeros 400 | pci_bus_id = line.split()[0].replace("0000:", "") 401 | logging.info(f"Found Nvidia GPU at {pci_bus_id}") 402 | break 403 | else: 404 | logging.error("Could not find Nvidia GPU") 405 | print("Try switching to hybrid mode first!") 406 | sys.exit(1) 407 | 408 | # need to return the BusID in 'PCI:bus:device:function' format 409 | # also perform hexadecimal to decimal conversion 410 | bus, device_function = pci_bus_id.split(":") 411 | device, function = device_function.split(".") 412 | return f"PCI:{int(bus, 16)}:{int(device, 16)}:{int(function, 16)}" 413 | 414 | 415 | def get_igpu_vendor(): 416 | lspci_output = subprocess.check_output(["lspci"]).decode('utf-8') 417 | for line in lspci_output.splitlines(): 418 | if 'VGA compatible controller' in line or 'Display controller' in line: 419 | if 'Intel' in line: 420 | logging.info("Found Intel iGPU") 421 | return 'intel' 422 | elif 'ATI' in line or 'AMD' in line or 'AMD/ATI' in line: 423 | logging.info("Found AMD iGPU") 424 | return 'amd' 425 | logging.warning("Could not find Intel or AMD iGPU") 426 | return None 427 | 428 | 429 | def get_display_manager(): 430 | try: 431 | with open('/etc/systemd/system/display-manager.service', 'r', encoding='utf-8') as f: 432 | content = f.read() 433 | match = re.search(r'ExecStart=(.+)\n', content) 434 | if match: 435 | # only return the final component of the path 436 | display_manager = os.path.basename(match.group(1)) 437 | logging.info(f"Found {display_manager} Display Manager") 438 | return display_manager 439 | except FileNotFoundError: 440 | logging.warning("Display Manager detection is not available") 441 | 442 | 443 | def generate_xrandr_script(igpu_vendor): 444 | if igpu_vendor == 'intel': 445 | return NVIDIA_XRANDR_SCRIPT.format('modesetting') 446 | elif igpu_vendor == 'amd': 447 | amd_igpu_name = get_amd_igpu_name() 448 | if amd_igpu_name != None: 449 | return NVIDIA_XRANDR_SCRIPT.format(amd_igpu_name) 450 | else: 451 | return NVIDIA_XRANDR_SCRIPT.format('modesetting') 452 | else: 453 | return NVIDIA_XRANDR_SCRIPT.format('modesetting') 454 | 455 | 456 | def get_amd_igpu_name(): 457 | if not os.path.exists('/usr/bin/xrandr'): 458 | logging.warning( 459 | "The 'xrandr' command is not available. Make sure the package is installed!") 460 | return None 461 | 462 | try: 463 | xrandr_output = subprocess.check_output( 464 | ['xrandr', '--listproviders']).decode('utf-8') 465 | except subprocess.CalledProcessError: 466 | logging.warning( 467 | "Failed to run the 'xrandr' command.") 468 | 469 | pattern = re.compile(r'(name:).*(ATI*|AMD*|AMD\/ATI)*') 470 | 471 | if pattern.findall(xrandr_output): 472 | return re.search(pattern, xrandr_output).group(0)[5:] 473 | else: 474 | logging.warning( 475 | "Could not find AMD iGPU in 'xrandr' output.") 476 | return None 477 | 478 | 479 | def rebuild_initramfs(): 480 | # OSTree systems first 481 | if any(os.path.exists(dir) for dir in ['/ostree', '/sysroot/ostree']): 482 | print('Rebuilding the initramfs with rpm-ostree...') 483 | command = ['rpm-ostree', 'initramfs', '--enable', '--arg=--force'] 484 | 485 | # Debian and Ubuntu derivatives 486 | elif os.path.exists('/etc/debian_version'): 487 | command = ['update-initramfs', '-u', '-k', 'all'] 488 | # RHEL and SUSE derivatives 489 | elif os.path.exists('/etc/redhat-release') or os.path.exists('/usr/bin/zypper'): 490 | command = ['dracut', '--force', '--regenerate-all'] 491 | # EndeavourOS with dracut 492 | elif os.path.exists('/usr/lib/endeavouros-release') and os.path.exists('/usr/bin/dracut'): 493 | command = ['dracut-rebuild'] 494 | # ALT Linux 495 | elif os.path.exists('/etc/altlinux-release'): 496 | command = ['make-initrd'] 497 | # Arch Linux 498 | elif os.path.exists('/etc/arch-release'): 499 | command = ['mkinitcpio', '-P'] 500 | else: 501 | command = [] 502 | 503 | if len(command) != 0: 504 | print('Rebuilding the initramfs...') 505 | if logging.getLogger().level == logging.DEBUG: 506 | p = subprocess.run(command) 507 | else: 508 | p = subprocess.run( 509 | command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 510 | if p.returncode == 0: 511 | print('Successfully rebuilt the initramfs!') 512 | else: 513 | logging.error("An error ocurred while rebuilding the initramfs") 514 | 515 | 516 | def create_file(path, content, executable=False): 517 | try: 518 | # create the parent folders if needed 519 | if not os.path.exists(os.path.dirname(path)): 520 | os.makedirs(os.path.dirname(path)) 521 | with open(path, mode='w', encoding='utf-8') as f: 522 | f.write(content) 523 | logging.info(f"Created file {path}") 524 | if logging.getLogger().level == logging.DEBUG: 525 | print(content) 526 | 527 | # add execution privilege 528 | if executable: 529 | subprocess.run(['chmod', '+x', path], stdout=subprocess.DEVNULL) 530 | logging.info(f"Added execution privilege to file {path}") 531 | except OSError as e: 532 | logging.error(f"Failed to create file '{path}': {e}") 533 | 534 | 535 | def assert_root(): 536 | if os.geteuid() != 0: 537 | logging.error("This operation requires root privileges") 538 | sys.exit(1) 539 | 540 | 541 | def main(): 542 | # define CLI arguments 543 | parser = argparse.ArgumentParser() 544 | parser.add_argument('-v', '--version', action='version', version=VERSION, 545 | help='Output the current version') 546 | parser.add_argument('-q', '--query', action='store_true', 547 | help='Query the current graphics mode') 548 | parser.add_argument('-s', '--switch', type=str, metavar='MODE', action='store', choices=SUPPORTED_MODES, 549 | help='Switch the graphics mode. Available choices: %(choices)s') 550 | parser.add_argument('--dm', type=str, metavar='DISPLAY_MANAGER', action='store', choices=SUPPORTED_DISPLAY_MANAGERS, 551 | help='Manually specify your Display Manager for Nvidia mode. Available choices: %(choices)s') 552 | parser.add_argument('--force-comp', action='store_true', 553 | help='Enable ForceCompositionPipeline on Nvidia mode') 554 | parser.add_argument('--coolbits', type=int, nargs='?', metavar='VALUE', action='store', const=28, 555 | help='Enable Coolbits on Nvidia mode. Default if specified: %(const)s') 556 | parser.add_argument('--rtd3', type=int, nargs='?', metavar='VALUE', action='store', choices=RTD3_MODES, const=2, 557 | help='Setup PCI-Express Runtime D3 (RTD3) Power Management on Hybrid mode. Available choices: %(choices)s. Default if specified: %(const)s') 558 | parser.add_argument('--use-nvidia-current', action='store_true', 559 | help='Use nvidia-current instead of nvidia for kernel modules') 560 | parser.add_argument('--reset-sddm', action='store_true', 561 | help='Restore default Xsetup file') 562 | parser.add_argument('--reset', action='store_true', 563 | help='Revert changes made by EnvyControl') 564 | parser.add_argument('--cache-create', action='store_true', 565 | help='Create cache used by EnvyControl; only works in hybrid mode') 566 | parser.add_argument('--cache-delete', action='store_true', 567 | help='Delete cache created by EnvyControl') 568 | parser.add_argument('--cache-query', action='store_true', 569 | help='Show cache created by EnvyControl') 570 | parser.add_argument('--verbose', default=False, action='store_true', 571 | help='Enable verbose mode') 572 | 573 | # print help if no arg is provided 574 | if len(sys.argv) == 1: 575 | parser.print_help() 576 | sys.exit(1) 577 | 578 | args = parser.parse_args() 579 | 580 | # log formatting 581 | logging.basicConfig(format='%(levelname)s: %(message)s') 582 | 583 | # set debug level for verbose mode 584 | if args.verbose: 585 | logging.getLogger().setLevel(logging.DEBUG) 586 | 587 | if args.query: 588 | mode = get_current_mode() 589 | print(mode) 590 | return 591 | elif args.cache_create: 592 | assert_root() 593 | CachedConfig(args).create_cache_file() 594 | return 595 | elif args.cache_delete: 596 | assert_root() 597 | CachedConfig.delete_cache_file() 598 | return 599 | elif args.cache_query: 600 | CachedConfig.show_cache_file() 601 | return 602 | 603 | if args.switch or args.reset_sddm or args.reset: 604 | with CachedConfig(args).adapter(): 605 | if args.switch: 606 | assert_root() 607 | graphics_mode_switcher( 608 | args.switch, args.dm, 609 | args.force_comp, args.coolbits, args.rtd3, args.use_nvidia_current 610 | ) 611 | elif args.reset_sddm: 612 | assert_root() 613 | create_file(SDDM_XSETUP_PATH, SDDM_XSETUP_CONTENT, True) 614 | print('Operation completed successfully') 615 | elif args.reset: 616 | assert_root() 617 | cleanup() 618 | CachedConfig.delete_cache_file() 619 | rebuild_initramfs() 620 | print('Operation completed successfully') 621 | 622 | 623 | class CachedConfig: 624 | '''Adapter for config from CACHE_FILE_PATH''' 625 | 626 | def __init__(self, app_args) -> None: 627 | self.app_args = app_args 628 | self.current_mode = get_current_mode() 629 | 630 | @contextmanager 631 | def adapter(self): 632 | global get_nvidia_gpu_pci_bus 633 | use_cache = os.path.exists(CACHE_FILE_PATH) 634 | 635 | if self.is_hybrid(): # recreate cache file when in hybrid mode 636 | self.create_cache_file() 637 | 638 | if use_cache: 639 | self.read_cache_file() # might not be in hybrid mode 640 | 641 | # rebind function to use cached value instead of detection 642 | get_nvidia_gpu_pci_bus = self.get_nvidia_gpu_pci_bus 643 | 644 | yield # back to main ... 645 | 646 | def create_cache_file(self): 647 | if not self.is_hybrid(): 648 | raise ValueError( 649 | '--cache-create requires that the system be in the hybrid Optimus mode') 650 | 651 | self.nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus() 652 | self.obj = self.create_cache_obj(self.nvidia_gpu_pci_bus) 653 | self.write_cache_file() 654 | 655 | def create_cache_obj(self, nvidia_gpu_pci_bus): 656 | return { 657 | 'nvidia_gpu_pci_bus': nvidia_gpu_pci_bus 658 | } 659 | 660 | def is_hybrid(self): 661 | return 'hybrid' == self.current_mode 662 | 663 | def get_nvidia_gpu_pci_bus(self): 664 | return self.nvidia_gpu_pci_bus 665 | 666 | @staticmethod 667 | def delete_cache_file(): 668 | os.remove(CACHE_FILE_PATH) 669 | os.removedirs(os.path.dirname(CACHE_FILE_PATH)) 670 | logging.debug(f"Removed file {CACHE_FILE_PATH}") 671 | 672 | def read_cache_file(self): 673 | from json import loads 674 | if os.path.exists(CACHE_FILE_PATH): 675 | with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f: 676 | content = f.read() 677 | self.obj = loads(content) 678 | self.nvidia_gpu_pci_bus = self.obj['nvidia_gpu_pci_bus'] 679 | elif self.is_hybrid(): 680 | self.nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus() 681 | else: 682 | raise ValueError( 683 | 'No cache present. Operation requires that the system be in the hybrid Optimus mode') 684 | 685 | @staticmethod 686 | def show_cache_file(): 687 | content = f'ERROR: Could not read {CACHE_FILE_PATH}' 688 | if os.path.exists(CACHE_FILE_PATH): 689 | with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f: 690 | content = f.read() 691 | print(content) 692 | 693 | def write_cache_file(self): 694 | from json import dump 695 | os.makedirs(os.path.dirname(CACHE_FILE_PATH), exist_ok=True) 696 | 697 | with open(CACHE_FILE_PATH, 'w', encoding='utf-8') as f: 698 | dump(self.obj, fp=f, indent=4, sort_keys=False) 699 | 700 | logging.debug(f"Created file {CACHE_FILE_PATH}") 701 | 702 | 703 | def get_current_mode(): 704 | mode = 'hybrid' 705 | if os.path.exists(BLACKLIST_PATH) and (os.path.exists(UDEV_INTEGRATED_PATH) or os.path.exists('/lib/udev/rules.d/50-remove-nvidia.rules')): 706 | mode = 'integrated' 707 | elif os.path.exists(XORG_PATH) and os.path.exists(MODESET_PATH): 708 | mode = 'nvidia' 709 | return mode 710 | 711 | 712 | if __name__ == '__main__': 713 | main() 714 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 3 | 4 | outputs = { self, nixpkgs }: 5 | let 6 | system = "x86_64-linux"; 7 | pkgs = nixpkgs.legacyPackages.${system}; 8 | in 9 | { 10 | packages.${system} = { 11 | envycontrol = pkgs.python3Packages.buildPythonPackage { 12 | pname = "envycontrol"; 13 | version = "3.5.2"; 14 | src = self; 15 | }; 16 | default = self.packages.${system}.envycontrol; 17 | }; 18 | 19 | buildInputs = with pkgs; [ pciutils ]; 20 | 21 | devShells.default = pkgs.mkShellNoCC { 22 | packages = with pkgs; [ 23 | (python3.withPackages(ps: with ps; [ setuptools ])) 24 | pciutils 25 | ]; 26 | }; 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /logos/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayasdev/envycontrol/7a375fc69e249b82cdc03a83a745387b8e3d5ebb/logos/dark.png -------------------------------------------------------------------------------- /logos/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bayasdev/envycontrol/7a375fc69e249b82cdc03a83a745387b8e3d5ebb/logos/light.png -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | import envycontrol 4 | 5 | 6 | setup( 7 | name='envycontrol', 8 | version=envycontrol.VERSION, 9 | description='Easy GPU switching for Nvidia Optimus laptops under Linux', 10 | url='http://github.com/bayasdev/envycontrol', 11 | author='Victor Bayas', 12 | author_email='victorsbayas@gmail.com', 13 | license='MIT', 14 | py_modules=['envycontrol'], 15 | entry_points={ 16 | 'console_scripts': [ 17 | 'envycontrol=envycontrol:main', 18 | ], 19 | }, 20 | keywords=['nvidia', 'optimus', 'prime', 'gpu', 'linux'], 21 | classifiers=[ 22 | 'Development Status :: 5 - Production/Stable', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Programming Language :: Python :: 3', 25 | 'Operating System :: POSIX :: Linux' 26 | ], 27 | ) --------------------------------------------------------------------------------