diff --git a/README.md b/README.md index 7a303d8..d26a695 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,9 @@ options: --use-nvidia-current Use nvidia-current instead of nvidia for kernel modules --reset-sddm Restore default Xsetup file --reset Revert changes made by EnvyControl + --cache-create Create cache used by EnvyControl; only works in hybrid mode + --cache-delete Delete cache created by EnvyControl + --cache-query Show cache created by EnvyControl --verbose Enable verbose mode ``` @@ -121,6 +124,55 @@ Revert all changes made by EnvyControl: sudo envycontrol --reset ``` +### Caching added with 3.4.0 +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. + +#### Cache file location + +```python +CACHE_FILE_PATH = '/var/cache/envycontrol/cache.json' +``` + +#### File format + +```json +{ + "nvidia_gpu_pci_bus": "PCI:1:0:0" +} +``` + +The cache is automatically re-created whenever a switch from hybrid mode is performed. + +#### Caching command line examples + +Create cache used by EnvyControl; only works in hybrid mode + +``` +sudo envycontrol --cache-create +``` + +When create cache is called when the system is in integrated or nvidia modes + +``` +sudo envycontrol --cache-create +... +ValueError: --cache-create requires that the system be in the hybrid Optimus mode +``` + + +Delete cache created by EnvyControl + +``` +sudo envycontrol --cache-delete +``` + +Show cache created by EnvyControl + +``` +sudo envycontrol --cache-query +``` + + ## ⬇️ Getting EnvyControl ### Arch Linux ([AUR](https://aur.archlinux.org/packages/envycontrol)) diff --git a/envycontrol.py b/envycontrol.py index f406884..6b8cfef 100755 --- a/envycontrol.py +++ b/envycontrol.py @@ -1,14 +1,18 @@ #!/usr/bin/env python3 import argparse -import sys +import logging import os import re import subprocess -import logging +import sys +from contextlib import contextmanager # begin constants definition -VERSION = '3.3.1' +VERSION = '3.4.0' + +# Note: Do NOT remove this in cleanup! +CACHE_FILE_PATH = '/var/cache/envycontrol/cache.json' BLACKLIST_PATH = '/etc/modprobe.d/blacklist-nvidia.conf' @@ -216,9 +220,12 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com if graphics_mode == 'integrated': if logging.getLogger().level == logging.DEBUG: - service = subprocess.run(["systemctl", "disable", "nvidia-persistenced.service"]) + service = subprocess.run( + ["systemctl", "disable", "nvidia-persistenced.service"]) else: - service = subprocess.run(["systemctl", "disable", "nvidia-persistenced.service"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + service = subprocess.run( + ["systemctl", "disable", "nvidia-persistenced.service"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if service.returncode == 0: print('Successfully disabled nvidia-persistenced.service') else: @@ -239,9 +246,12 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com cleanup() if logging.getLogger().level == logging.DEBUG: - service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"]) + service = subprocess.run( + ["systemctl", "enable", "nvidia-persistenced.service"]) else: - service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + service = subprocess.run( + ["systemctl", "enable", "nvidia-persistenced.service"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if service.returncode == 0: print('Successfully enabled nvidia-persistenced.service') else: @@ -255,7 +265,8 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com else: # setup rtd3 if use_nvidia_current: - create_file(MODESET_PATH, MODESET_CURRENT_RTD3.format(rtd3_value)) + create_file( + MODESET_PATH, MODESET_CURRENT_RTD3.format(rtd3_value)) else: create_file(MODESET_PATH, MODESET_RTD3.format(rtd3_value)) create_file(UDEV_PM_PATH, UDEV_PM_CONTENT) @@ -266,9 +277,12 @@ def graphics_mode_switcher(graphics_mode, user_display_manager, enable_force_com print(f"Enable Coolbits: {coolbits_value or False}") if logging.getLogger().level == logging.DEBUG: - service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"]) + service = subprocess.run( + ["systemctl", "enable", "nvidia-persistenced.service"]) else: - service = subprocess.run(["systemctl", "enable", "nvidia-persistenced.service"],stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + service = subprocess.run( + ["systemctl", "enable", "nvidia-persistenced.service"], + stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) if service.returncode == 0: print('Successfully enabled nvidia-persistenced.service') else: @@ -522,6 +536,12 @@ def main(): help='Restore default Xsetup file') parser.add_argument('--reset', action='store_true', help='Revert changes made by EnvyControl') + parser.add_argument('--cache-create', action='store_true', + help='Create cache used by EnvyControl; only works in hybrid mode') + parser.add_argument('--cache-delete', action='store_true', + help='Delete cache created by EnvyControl') + parser.add_argument('--cache-query', action='store_true', + help='Show cache created by EnvyControl') parser.add_argument('--verbose', default=False, action='store_true', help='Enable verbose mode') @@ -540,26 +560,128 @@ def main(): logging.getLogger().setLevel(logging.DEBUG) if args.query: - if os.path.exists(BLACKLIST_PATH) and os.path.exists(UDEV_INTEGRATED_PATH): - mode = 'integrated' - elif os.path.exists(XORG_PATH) and os.path.exists(MODESET_PATH): - mode = 'nvidia' - else: - mode = 'hybrid' + mode = get_current_mode() print(mode) - elif args.switch: - assert_root() - graphics_mode_switcher(args.switch, args.dm, - args.force_comp, args.coolbits, args.rtd3, args.use_nvidia_current) - elif args.reset_sddm: + return + elif args.cache_create: assert_root() - create_file(SDDM_XSETUP_PATH, SDDM_XSETUP_CONTENT, True) - print('Operation completed successfully') - elif args.reset: + CachedConfig(args).create_cache_file() + return + elif args.cache_delete: assert_root() - cleanup() - rebuild_initramfs() - print('Operation completed successfully') + CachedConfig.delete_cache_file() + return + elif args.cache_query: + CachedConfig.show_cache_file() + return + + if args.switch or args.reset_sddm or args.reset: + with CachedConfig(args).adapter(): + if args.switch: + assert_root() + graphics_mode_switcher( + args.switch, args.dm, + args.force_comp, args.coolbits, args.rtd3, args.use_nvidia_current + ) + elif args.reset_sddm: + assert_root() + create_file(SDDM_XSETUP_PATH, SDDM_XSETUP_CONTENT, True) + print('Operation completed successfully') + elif args.reset: + assert_root() + cleanup() + CachedConfig.delete_cache_file() + rebuild_initramfs() + print('Operation completed successfully') + + +class CachedConfig: + '''Adapter for config from CACHE_FILE_PATH''' + + def __init__(self, app_args) -> None: + self.app_args = app_args + self.current_mode = get_current_mode() + + @contextmanager + def adapter(self): + global get_nvidia_gpu_pci_bus + use_cache = os.path.exists(CACHE_FILE_PATH) + + if self.is_hybrid(): # recreate cache file when in hybrid mode + self.create_cache_file() + + if use_cache: + self.read_cache_file() # might not be in hybrid mode + + # rebind function to use cached value instead of detection + get_nvidia_gpu_pci_bus = self.get_nvidia_gpu_pci_bus + + yield # back to main ... + + def create_cache_file(self): + if not self.is_hybrid(): + raise ValueError( + '--cache-create requires that the system be in the hybrid Optimus mode') + + self.nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus() + self.obj = self.create_cache_obj(self.nvidia_gpu_pci_bus) + self.write_cache_file() + + def create_cache_obj(self, nvidia_gpu_pci_bus): + return { + 'nvidia_gpu_pci_bus': nvidia_gpu_pci_bus + } + + def is_hybrid(self): + return 'hybrid' == self.current_mode + + def get_nvidia_gpu_pci_bus(self): + return self.nvidia_gpu_pci_bus + + @staticmethod + def delete_cache_file(): + os.remove(CACHE_FILE_PATH) + os.removedirs(os.path.dirname(CACHE_FILE_PATH)) + logging.debug(f"Removed file {CACHE_FILE_PATH}") + + def read_cache_file(self): + from json import loads + if os.path.exists(CACHE_FILE_PATH): + with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f: + content = f.read() + self.obj = loads(content) + self.nvidia_gpu_pci_bus = self.obj['nvidia_gpu_pci_bus'] + elif self.is_hybrid(): + self.nvidia_gpu_pci_bus = get_nvidia_gpu_pci_bus() + else: + raise ValueError( + 'No cache present. Operation requires that the system be in the hybrid Optimus mode') + + @staticmethod + def show_cache_file(): + content = f'ERROR: Could not read {CACHE_FILE_PATH}' + if os.path.exists(CACHE_FILE_PATH): + with open(CACHE_FILE_PATH, 'r', encoding='utf-8') as f: + content = f.read() + print(content) + + def write_cache_file(self): + from json import dump + os.makedirs(os.path.dirname(CACHE_FILE_PATH), exist_ok=True) + + with open(CACHE_FILE_PATH, 'w', encoding='utf-8') as f: + dump(self.obj, fp=f, indent=4, sort_keys=False) + + logging.debug(f"Created file {CACHE_FILE_PATH}") + + +def get_current_mode(): + mode = 'hybrid' + if os.path.exists(BLACKLIST_PATH) and os.path.exists(UDEV_INTEGRATED_PATH): + mode = 'integrated' + elif os.path.exists(XORG_PATH) and os.path.exists(MODESET_PATH): + mode = 'nvidia' + return mode if __name__ == '__main__':