├── .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 |
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 | 
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 | 
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 | )
--------------------------------------------------------------------------------