diff --git a/.gitignore b/.gitignore index 1efd599..68a02f3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +*.AppImage # PyInstaller # Usually these files are written by a python script from a template diff --git a/README.md b/README.md index 0a475a4..4bd31b2 100644 --- a/README.md +++ b/README.md @@ -59,18 +59,25 @@ These will be passed to the executed program. Example: - Each profile has its own set of configurations, which can be applied through the program or system tray. ## Build/Test Requirements: - +- Linux operating system - Python 3.9 or higher - Pip -- The `python3-venv` package its required on Debian/Debian based distros. -- Linux operating system - -## Additional requirements in the case you build the program using Nuitka: +- The `python3-venv` package is required on Debian/Debian based distros. +- bash +- tar +- coreutils +- shasum (for dependency hash checking) +## Additional Requirements for Building with Nuitka: - C/C++ Compiler - patchelf - ccache (optional, for optimizing compiling times) +## Additional Requirements for Creating AppImage: +- fuse or fuse3 +- wget +- chmod + ## Additional requirements for some Options: If this software is not provided, its options will be locked. @@ -126,20 +133,32 @@ If this software is not provided, its options will be locked. In the case you want to contribute to the project you can use the provided `test.sh` script to test the changes you made. This script will create a Python virtual environment if one does not already exist. This way, you don't have to install the program dependencies systemwide. -The first time you run it, use the -c flag that will also copy the `volt-helper` to `/usr/local/bin/`, as the program requires it for appliying the settings: +The first time you run it, use the -c flag and sudo, that will copy the `volt-helper` to `/usr/local/bin/`, as the program requires it for appliying the settings: ``` -./test.sh -c +sudo ./test.sh -c ``` -After this unless you make changes to the `volt-helper`, or the script have been updated, just run it without the flag to avoid unnecessary overwrites of the script: +After this unless you make changes to the `volt-helper`, or the script have been updated, just run it without the flag and sudo, this will create the `py_env` folder and run the program: ``` ./test.sh ``` +To delete the `py_env` folder you can use: + +``` +./test.sh -r +``` + +or: + +``` +sudo ./test.sh -r +``` + > [!NOTE] -> You can use the `remove.sh` script to remove the `volt-helper`. The `py_env` folder should be deleted in the case you created it with your system python, and you want to use a python version that its inside a `distrobox` box, or vice versa. +> You can use the `remove.sh` script to remove the `volt-helper`. The `py_env` folder should be deleted if it becomes corrupted, or if it was created with your system python, and you want to use a python version that its inside a `distrobox` box, or vice versa. ## How to use `volt-gui`: diff --git a/install.sh b/install.sh index 1f6ecb9..15e3c35 100755 --- a/install.sh +++ b/install.sh @@ -2,43 +2,49 @@ set -euo pipefail -if [[ $EUID -ne 0 ]]; then - echo -e "\033[31mError: Please run this script as root (use sudo)\033[0m" >&2 - exit 1 -fi - +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" INSTALL_DIR="/usr/local/bin" BIN_DIR="bin" EXECUTABLE="$BIN_DIR/volt-gui" -HELPER_SCRIPT="scripts/volt-helper" DESKTOP_FILE="/usr/share/applications/volt-gui.desktop" -if [[ ! -d "$BIN_DIR" ]]; then - echo -e "\033[31mError: bin directory not found. Run make-pyinstaller.sh or make-nuitka.sh first.\033[0m" >&2 +check_commands() { + for cmd in install mkdir cat update-desktop-database dirname; do + if ! command -v "$cmd" &> /dev/null; then + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 + exit 1 + fi + done +} + +if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Error: Please run this script as root (use sudo)${NC}" >&2 exit 1 fi -if [[ ! -f "$EXECUTABLE" ]]; then - echo -e "\033[31mError: Executable 'volt-gui' not found in bin directory. Run make-pyinstaller.sh or make-nuitka.sh first.\033[0m" >&2 +check_commands + +if [[ ! -d "$BIN_DIR" ]]; then + echo -e "${RED}Error: bin directory not found. Run make-pyinstaller.sh or make-nuitka.sh first.${NC}" >&2 exit 1 fi -if [[ ! -f "$HELPER_SCRIPT" ]]; then - echo -e "\033[31mError: Helper script $HELPER_SCRIPT not found.\033[0m" >&2 +if [[ ! -f "$EXECUTABLE" ]]; then + echo -e "${RED}Error: Executable "volt-gui" not found in bin directory. Run make-pyinstaller.sh or make-nuitka.sh first.${NC}" >&2 exit 1 fi -echo -e "\033[34mInstalling main executable...\033[0m" +echo -e "${BLUE}Installing main executable...${NC}" install -v -m 755 -T "$EXECUTABLE" "$INSTALL_DIR/volt-gui" -echo -e "\n\033[34mInstalling helper script...\033[0m" -install -v -m 755 -T "$HELPER_SCRIPT" "$INSTALL_DIR/volt-helper" - -echo -e "\n\033[34mCreating desktop entry...\033[0m" +echo -e "\n${BLUE}Creating desktop entry...${NC}" +mkdir -p "$(dirname "$DESKTOP_FILE")" cat > "$DESKTOP_FILE" << EOF [Desktop Entry] Name=volt-gui -Comment=A simple GUI program to modify and create the "volt" script and more +Comment=My AMD Adrenaline / NVIDIA Settings Linux Alternative Exec=volt-gui Icon=preferences-system Terminal=false @@ -48,8 +54,8 @@ EOF echo "Desktop entry created at $DESKTOP_FILE" -echo -e "\n\033[34mUpdating desktop database...\033[0m" +echo -e "\n${BLUE}Updating desktop database...${NC}" update-desktop-database "$(dirname "$DESKTOP_FILE")" -echo -e "\n\033[32mInstallation completed successfully!\033[0m" -echo "You can now run 'volt-gui' from the terminal or application menu." +echo -e "\nInstallation completed successfully!" +echo "You can now run "volt-gui" from the terminal or application menu." diff --git a/make-appimage.sh b/make-appimage.sh new file mode 100755 index 0000000..2d4eb69 --- /dev/null +++ b/make-appimage.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +set -euo pipefail + +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" +APP_NAME="volt-gui" +BIN_DIR="bin" +EXECUTABLE="$BIN_DIR/volt-gui" +APPDIR="AppDir" +DESKTOP_FILE="volt-gui.desktop" +ICON_FILE="preferences-system.png" +SOURCE_ICON="images/1.png" +APPIMAGETOOL="appimagetool-x86_64.AppImage" +OUTPUT_FILE="${APP_NAME}-x86_64.AppImage" + +cleanup() { + rm -rf "$APPDIR" 2>/dev/null || true +} + +check_commands() { + for cmd in wget chmod mkdir cp cat dirname readlink du cut; do + if ! command -v "$cmd" &> /dev/null; then + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 + exit 1 + fi + done +} + +verify_files() { + if [[ ! -f "$EXECUTABLE" ]]; then + echo -e "${RED}Error: Executable $EXECUTABLE not found${NC}" >&2 + echo "Please run the build script first to create the executable" >&2 + exit 1 + fi + if [[ ! -f "$SOURCE_ICON" ]]; then + echo -e "${RED}Error: Icon file $SOURCE_ICON not found${NC}" >&2 + exit 1 + fi +} + +create_appdir_structure() { + echo -e "${BLUE}Creating AppDir structure...${NC}" + mkdir -p "$APPDIR" +} + +copy_icon() { + echo -e "${BLUE}Copying icon...${NC}" + cp "$SOURCE_ICON" "$APPDIR/$ICON_FILE" +} + +create_desktop_file() { + echo -e "${BLUE}Creating desktop file...${NC}" + cat > "$APPDIR/$DESKTOP_FILE" << "EOF" +[Desktop Entry] +Name=volt-gui +Comment=My AMD Adrenaline / NVIDIA Settings Linux Alternative +Exec=volt-gui +Icon=preferences-system +Terminal=false +Type=Application +Categories=Utility; +EOF +} + +create_apprun() { + echo -e "${BLUE}Creating AppRun script...${NC}" + cat > "$APPDIR/AppRun" << "EOF" +#!/bin/bash +HERE="$(dirname "$(readlink -f "${0}")")" +export APPDIR="${HERE}" +cd "${HOME}" 2>/dev/null || cd /tmp +exec "${HERE}/volt-gui" "$@" +EOF + chmod +x "$APPDIR/AppRun" +} + +copy_executable() { + echo -e "${BLUE}Copying executable...${NC}" + cp "$EXECUTABLE" "$APPDIR/$APP_NAME" + chmod +x "$APPDIR/$APP_NAME" +} + +download_appimagetool() { + if [[ ! -f "$APPIMAGETOOL" ]]; then + echo -e "${BLUE}Downloading appimagetool...${NC}" + wget -q --show-progress \ + "https://github.com/AppImage/AppImageKit/releases/download/continuous/$APPIMAGETOOL" + chmod +x "$APPIMAGETOOL" + else + echo "appimagetool already downloaded" + fi +} + +build_appimage() { + echo -e "${BLUE}Building AppImage...${NC}" + if ! ./"$APPIMAGETOOL" "$APPDIR" "$OUTPUT_FILE"; then + echo -e "${RED}Error: Failed to build AppImage${NC}" >&2 + exit 1 + fi + chmod +x "$OUTPUT_FILE" +} + +print_success() { + local size="" + + echo -e "\nBuild successful!" + echo -e "AppImage: $OUTPUT_FILE" + if command -v du &> /dev/null; then + size=$(du -h "$OUTPUT_FILE" 2>/dev/null | cut -f1 || echo "Unknown") + echo -e "File size: $size" + fi +} + +main() { + trap cleanup EXIT + check_commands + verify_files + create_appdir_structure + copy_icon + create_desktop_file + copy_executable + create_apprun + download_appimagetool + build_appimage + print_success +} + +main "$@" diff --git a/make-nuitka.sh b/make-nuitka.sh index 8c77834..26c862e 100755 --- a/make-nuitka.sh +++ b/make-nuitka.sh @@ -2,19 +2,15 @@ set -euo pipefail -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" VENV_DIR="py_env" REQ_FILE="requirements.txt" REQ_HASH_FILE="$VENV_DIR/requirements.sha256" SRC_FILE="src/volt-gui.py" BIN_DIR="bin" BASE_FILENAME=$(basename "$SRC_FILE" .py) - NUITKA_OPTS=( "--onefile" "--output-filename=$BASE_FILENAME" @@ -27,10 +23,9 @@ cleanup() { } check_commands() { - local commands=("python3" "pip") - for cmd in "${commands[@]}"; do + for cmd in python3 pip shasum cut cat basename mkdir mv du; do if ! command -v "$cmd" &> /dev/null; then - echo -e "${RED}Error: Required command '$cmd' not found${NC}" >&2 + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 exit 1 fi done @@ -38,7 +33,7 @@ check_commands() { create_venv() { if [[ ! -d "$VENV_DIR" ]]; then - echo -e "${CYAN}Creating python3 virtual environment...${NC}" + echo -e "${BLUE}Creating python3 virtual environment...${NC}" python3 -m venv "$VENV_DIR" fi } @@ -51,12 +46,14 @@ verify_requirements() { } update_dependencies() { - local current_hash stored_hash - current_hash=$(shasum -a 256 "$REQ_FILE" | cut -d' ' -f1) + local current_hash="" + local stored_hash="" + + current_hash=$(shasum -a 256 "$REQ_FILE" | cut -d" " -f1) stored_hash=$(cat "$REQ_HASH_FILE" 2>/dev/null || true) if [[ ! -f "$REQ_HASH_FILE" ]] || [[ "$current_hash" != "$stored_hash" ]]; then - echo -e "${CYAN}Updating dependencies...${NC}" + echo -e "${BLUE}Updating dependencies...${NC}" pip install --upgrade pip pip install --no-cache-dir -r "$REQ_FILE" echo "$current_hash" > "$REQ_HASH_FILE" @@ -64,9 +61,8 @@ update_dependencies() { } build_executable() { - echo -e "${CYAN}Building executable with Nuitka...${NC}" - echo -e "${YELLOW}Nuitka options: ${NUITKA_OPTS[*]}${NC}" - + echo -e "${BLUE}Building executable with Nuitka...${NC}" + echo -e "Nuitka options: ${NUITKA_OPTS[*]}" if ! nuitka "${NUITKA_OPTS[@]}" "$SRC_FILE"; then echo -e "${RED}Error: Nuitka failed to build executable${NC}" >&2 exit 1 @@ -79,24 +75,22 @@ move_to_bin() { } main() { + local size="" + trap cleanup EXIT check_commands verify_requirements create_venv - - echo -e "${CYAN}Activating virtual environment...${NC}" + echo -e "${BLUE}Activating virtual environment...${NC}" source "$VENV_DIR/bin/activate" - update_dependencies build_executable move_to_bin - - echo -e "\n${GREEN}Build successful!${NC}" - echo -e "Executable: ${YELLOW}$BIN_DIR/$(basename "$BASE_FILENAME")${NC}" - + echo -e "\nBuild successful!" + echo -e "Executable: $BIN_DIR/$BASE_FILENAME" if command -v du &> /dev/null; then - local size=$(du -h "$BIN_DIR"/* 2>/dev/null | cut -f1 || echo "Unknown") - echo -e "File size: ${YELLOW}$size${NC}" + size=$(du -h "$BIN_DIR"/* 2>/dev/null | cut -f1 || echo "Unknown") + echo -e "File size: $size" fi } diff --git a/make-pyinstaller.sh b/make-pyinstaller.sh index 624fcb1..753513a 100755 --- a/make-pyinstaller.sh +++ b/make-pyinstaller.sh @@ -2,12 +2,9 @@ set -euo pipefail -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" VENV_DIR="py_env" REQ_FILE="requirements.txt" REQ_HASH_FILE="$VENV_DIR/requirements.sha256" @@ -15,7 +12,6 @@ SRC_FILE="src/volt-gui.py" BIN_DIR="bin" BASE_FILENAME=$(basename "$SRC_FILE" .py) SPEC_FILE="$BASE_FILENAME.spec" - PYINSTALLER_OPTS=( "--onefile" "--name=volt-gui" @@ -26,10 +22,9 @@ cleanup() { } check_commands() { - local commands=("python3" "pip") - for cmd in "${commands[@]}"; do + for cmd in python3 pip shasum cut cat basename mkdir mv du; do if ! command -v "$cmd" &> /dev/null; then - echo -e "${RED}Error: Required command '$cmd' not found${NC}" >&2 + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 exit 1 fi done @@ -37,7 +32,7 @@ check_commands() { create_venv() { if [[ ! -d "$VENV_DIR" ]]; then - echo -e "${CYAN}Creating python3 virtual environment...${NC}" + echo -e "${BLUE}Creating python3 virtual environment...${NC}" python3 -m venv "$VENV_DIR" fi } @@ -50,12 +45,14 @@ verify_requirements() { } update_dependencies() { - local current_hash stored_hash - current_hash=$(shasum -a 256 "$REQ_FILE" | cut -d' ' -f1) + local current_hash="" + local stored_hash="" + + current_hash=$(shasum -a 256 "$REQ_FILE" | cut -d" " -f1) stored_hash=$(cat "$REQ_HASH_FILE" 2>/dev/null || true) if [[ ! -f "$REQ_HASH_FILE" ]] || [[ "$current_hash" != "$stored_hash" ]]; then - echo -e "${CYAN}Updating dependencies...${NC}" + echo -e "${BLUE}Updating dependencies...${NC}" pip install --upgrade pip pip install --no-cache-dir -r "$REQ_FILE" echo "$current_hash" > "$REQ_HASH_FILE" @@ -63,9 +60,8 @@ update_dependencies() { } build_executable() { - echo -e "${CYAN}Building executable with PyInstaller...${NC}" - echo -e "${YELLOW}PyInstaller options: ${PYINSTALLER_OPTS[*]}${NC}" - + echo -e "${BLUE}Building executable with PyInstaller...${NC}" + echo -e "PyInstaller options: ${PYINSTALLER_OPTS[*]}" if ! pyinstaller "${PYINSTALLER_OPTS[@]}" "$SRC_FILE"; then echo -e "${RED}Error: PyInstaller failed to build executable${NC}" >&2 exit 1 @@ -78,24 +74,22 @@ move_to_bin() { } main() { + local size="" + trap cleanup EXIT check_commands verify_requirements create_venv - - echo -e "${CYAN}Activating virtual environment...${NC}" + echo -e "${BLUE}Activating virtual environment...${NC}" source "$VENV_DIR/bin/activate" - update_dependencies build_executable move_to_bin - - echo -e "\n${GREEN}Build successful!${NC}" - echo -e "Executable: ${YELLOW}$BIN_DIR/$BASE_FILENAME${NC}" - + echo -e "\nBuild successful!" + echo -e "Executable: $BIN_DIR/$BASE_FILENAME" if command -v du &> /dev/null; then - local size=$(du -h "$BIN_DIR"/* 2>/dev/null | cut -f1 || echo "Unknown") - echo -e "File size: ${YELLOW}$size${NC}" + size=$(du -h "$BIN_DIR"/* 2>/dev/null | cut -f1 || echo "Unknown") + echo -e "File size: $size" fi } diff --git a/make-release.sh b/make-release.sh index 2dce7c4..7925a89 100755 --- a/make-release.sh +++ b/make-release.sh @@ -2,56 +2,74 @@ set -euo pipefail -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" RELEASE_DIR="releases" PYINSTALLER_BUILD="volt-gui-pyinstaller" NUITKA_BUILD="volt-gui-nuitka" BUILD_SCRIPTS=("make-pyinstaller.sh" "make-nuitka.sh") +APPIMAGE_SCRIPT="make-appimage.sh" +ORIGINAL_DIR=$(pwd) cleanup() { true } check_commands() { - local commands=("tar" "cp" "mkdir") - for cmd in "${commands[@]}"; do + for cmd in tar cp mkdir mv pwd cd du sed rm; do if ! command -v "$cmd" &> /dev/null; then - echo -e "${RED}Error: Required command '$cmd' not found${NC}" >&2 + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 exit 1 fi done } build_and_copy() { - local build_script=$1 - local target_dir=$2 - - echo -e "${CYAN}Executing build script: $build_script${NC}" - if ! (cd .. && ./"$build_script"); then + local build_script="$1" + local target_dir="$2" + local build_type="$3" + local appimage_file="volt-gui-x86_64.AppImage" + local renamed_appimage="volt-gui-${build_type}-x86_64.AppImage" + + echo -e "${BLUE}Executing build script: $build_script${NC}" + if ! (cd "$ORIGINAL_DIR" && ./"$build_script"); then echo -e "${RED}Error: Build script $build_script failed${NC}" >&2 exit 1 fi - echo -e "${CYAN}Copying artifacts to $target_dir${NC}" - mkdir -p "$target_dir" + echo -e "${BLUE}Building AppImage for $build_type${NC}" + if ! (cd "$ORIGINAL_DIR" && ./"$APPIMAGE_SCRIPT"); then + echo -e "${RED}Error: AppImage build failed${NC}" >&2 + exit 1 + fi + + echo -e "${BLUE}Renaming AppImage to $renamed_appimage${NC}" + if [[ -f "$ORIGINAL_DIR/$appimage_file" ]]; then + mv "$ORIGINAL_DIR/$appimage_file" "$ORIGINAL_DIR/$renamed_appimage" + else + echo -e "${RED}Error: AppImage file not found${NC}" >&2 + exit 1 + fi - for item in bin install.sh remove.sh scripts; do - if [[ -e "../$item" ]]; then - cp -r "../$item" "$target_dir/" + echo -e "${BLUE}Copying artifacts to $target_dir${NC}" + mkdir -p "$target_dir" + for item in bin install.sh remove.sh; do + if [[ -e "$ORIGINAL_DIR/$item" ]]; then + cp -r "$ORIGINAL_DIR/$item" "$target_dir/" else - echo -e "${YELLOW}Warning: $item not found, skipping${NC}" + echo "Warning: $item not found, skipping" fi done + + echo -e "${BLUE}Moving AppImage to release directory${NC}" + mv "$ORIGINAL_DIR/$renamed_appimage" . } compress_release() { - local dir_name=$1 - echo -e "${CYAN}Compressing $dir_name to ${dir_name}.tar.gz${NC}" + local dir_name="$1" + + echo -e "${BLUE}Compressing $dir_name to ${dir_name}.tar.gz${NC}" tar -czf "${dir_name}.tar.gz" "$dir_name" } @@ -59,31 +77,34 @@ main() { trap cleanup EXIT check_commands - ORIGINAL_DIR=$(pwd) - - echo -e "${CYAN}Preparing release directory...${NC}" + echo -e "${BLUE}Preparing release directory...${NC}" rm -rf "$RELEASE_DIR" mkdir -p "$RELEASE_DIR" cd "$RELEASE_DIR" - echo -e "\n${YELLOW}=== Processing PyInstaller Build ===${NC}" - build_and_copy "${BUILD_SCRIPTS[0]}" "$PYINSTALLER_BUILD" + echo -e "\n=== Processing PyInstaller Build ===" + build_and_copy "${BUILD_SCRIPTS[0]}" "$PYINSTALLER_BUILD" "pyinstaller" compress_release "$PYINSTALLER_BUILD" - echo -e "\n${YELLOW}=== Processing Nuitka Build ===${NC}" - build_and_copy "${BUILD_SCRIPTS[1]}" "$NUITKA_BUILD" + echo -e "\n=== Processing Nuitka Build ===" + build_and_copy "${BUILD_SCRIPTS[1]}" "$NUITKA_BUILD" "nuitka" compress_release "$NUITKA_BUILD" cd "$ORIGINAL_DIR" - echo -e "\n${GREEN}Release build completed successfully!${NC}" - echo -e "Created archives in ${YELLOW}$RELEASE_DIR${NC}:" - echo -e " ${YELLOW}${PYINSTALLER_BUILD}.tar.gz${NC}" - echo -e " ${YELLOW}${NUITKA_BUILD}.tar.gz${NC}" + echo -e "\nRelease build completed successfully!" + echo -e "Created archives in $RELEASE_DIR:" + echo -e " ${PYINSTALLER_BUILD}.tar.gz" + echo -e " ${NUITKA_BUILD}.tar.gz" + echo -e "\nCreated AppImages in $RELEASE_DIR:" + echo -e " volt-gui-pyinstaller-x86_64.AppImage" + echo -e " volt-gui-nuitka-x86_64.AppImage" if command -v du &> /dev/null; then echo -e "\nArchive sizes:" - du -h "$RELEASE_DIR"/*.tar.gz | sed 's/^/ /' + du -h "$RELEASE_DIR"/*.tar.gz | sed "s/^/ /" + echo -e "\nAppImage sizes:" + du -h "$RELEASE_DIR"/*.AppImage | sed "s/^/ /" fi } diff --git a/remove.sh b/remove.sh index a5355a1..e2c40bb 100755 --- a/remove.sh +++ b/remove.sh @@ -2,32 +2,47 @@ set -euo pipefail -if [[ $EUID -ne 0 ]]; then - echo -e "\033[31mError: Please run this script as root (use sudo)\033[0m" >&2 - exit 1 -fi - +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" INSTALL_DIR="/usr/local/bin" TARGETS=("volt" "volt-gui" "volt-helper") DESKTOP_FILE="/usr/share/applications/volt-gui.desktop" +FILE="" + +check_commands() { + for cmd in rm dirname update-desktop-database; do + if ! command -v "$cmd" &> /dev/null; then + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 + exit 1 + fi + done +} + +if [[ $EUID -ne 0 ]]; then + echo -e "${RED}Error: Please run this script as root (use sudo)${NC}" >&2 + exit 1 +fi + +check_commands -echo -e "\033[34mRemoving installed files...\033[0m" +echo -e "${BLUE}Removing installed files...${NC}" for target in "${TARGETS[@]}"; do - file="$INSTALL_DIR/$target" - if [[ -f "$file" ]]; then - rm -v "$file" - else - echo -e "\033[33mWarning: $file not found\033[0m" - fi + FILE="$INSTALL_DIR/$target" + if [[ -f "$FILE" ]]; then + rm -v "$FILE" + else + echo "Warning: $FILE not found" + fi done if [[ -f "$DESKTOP_FILE" ]]; then - rm -v "$DESKTOP_FILE" - echo -e "\n\033[34mUpdating desktop database...\033[0m" - update-desktop-database "$(dirname "$DESKTOP_FILE")" + rm -v "$DESKTOP_FILE" + echo -e "\n${BLUE}Updating desktop database...${NC}" + update-desktop-database "$(dirname "$DESKTOP_FILE")" else - echo -e "\033[33mWarning: Desktop entry $DESKTOP_FILE not found\033[0m" + echo "Warning: Desktop entry $DESKTOP_FILE not found" fi -echo -e "\n\033[32mRemoval completed successfully!\033[0m" +echo -e "\nRemoval completed successfully!" diff --git a/scripts/volt-helper b/scripts/volt-helper deleted file mode 100755 index 713123c..0000000 --- a/scripts/volt-helper +++ /dev/null @@ -1,239 +0,0 @@ -#!/bin/bash - -SCRIPT_NAME="$0" - -apply_governor() { - local governor="$1" - - for CPU_PATH in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do - echo "$governor" > "$CPU_PATH" - done -} - -apply_max_freq() { - local max_freq="$1" - for CPU_PATH in /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; do - echo "$max_freq" > "$CPU_PATH" - done -} - -apply_min_freq() { - local min_freq="$1" - for CPU_PATH in /sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq; do - echo "$min_freq" > "$CPU_PATH" - done -} - -terminate_existing_schedulers() { - local scheduler_pid=$(pgrep -f '^scx_' 2>/dev/null | head -n1) - - if [ -z "$scheduler_pid" ]; then - return - fi - - kill -INT "$scheduler_pid" 2>/dev/null - sleep 0.5 - - if kill -0 "$scheduler_pid" 2>/dev/null; then - kill -TERM "$scheduler_pid" 2>/dev/null - sleep 0.5 - fi - - if kill -0 "$scheduler_pid" 2>/dev/null; then - kill -KILL "$scheduler_pid" 2>/dev/null - sleep 0.2 - fi -} - -start_new_scheduler() { - local scheduler="$1" - - "$scheduler" & - local scheduler_pid=$! - sleep 1 -} - -handle_scheduler() { - terminate_existing_schedulers - - if [ -n "$scheduler" ] && [ "$scheduler" != "none" ]; then - start_new_scheduler "$scheduler" - fi -} - -manage_cpu() { - local cpu_args=("$@") - - local governor="" - local scheduler="" - local max_freq="" - local min_freq="" - - for arg in "${cpu_args[@]}"; do - if [[ "$arg" == governor:* ]]; then - governor="${arg#governor:}" - elif [[ "$arg" == scheduler:* ]]; then - scheduler="${arg#scheduler:}" - elif [[ "$arg" == max_freq:* ]]; then - max_freq="${arg#max_freq:}" - elif [[ "$arg" == min_freq:* ]]; then - min_freq="${arg#min_freq:}" - fi - done - - if [ -n "$governor" ]; then - apply_governor "$governor" - fi - - if [ -n "$min_freq" ]; then - apply_min_freq "$min_freq" - fi - - if [ -n "$max_freq" ]; then - apply_max_freq "$max_freq" - fi - - if [ -n "$scheduler" ]; then - handle_scheduler "$scheduler" - fi -} - -apply_disk_scheduler() { - local disk_name="$1" - local scheduler="$2" - local scheduler_path="/sys/block/$disk_name/queue/scheduler" - - echo "$scheduler" > "$scheduler_path" -} - -manage_disk() { - local disk_args=("$@") - - for arg in "${disk_args[@]}"; do - if [[ "$arg" == *":"* ]]; then - local disk_name="${arg%%:*}" - local scheduler="${arg#*:}" - - apply_disk_scheduler "$disk_name" "$scheduler" - fi - done -} - -apply_kernel_parameter() { - local path="$1" - local value="$2" - - echo "$value" > "$path" 2>/dev/null -} - -manage_kernel() { - local kernel_args=("$@") - - for setting in "${kernel_args[@]}"; do - local path="${setting%%:*}" - local value="${setting#*:}" - - apply_kernel_parameter "$path" "$value" - done -} - -read_gpu_settings() { - local settings_file="$1" - local script_content="#!/bin/bash\n\n" - - while IFS='=' read -r key value || [ -n "$key" ]; do - if [ -z "$key" ] || [[ "$key" =~ ^[[:space:]]*# ]]; then - continue - fi - - key=$(echo "$key" | tr -d ' ') - value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - - if [ "$key" = "launch_options" ]; then - continue - elif [[ "$key" == unset:* ]]; then - script_content="${script_content}unset ${key#unset:}\n" - elif [ -n "$value" ]; then - script_content="${script_content}export ${key}=\"${value}\"\n" - fi - done < "$settings_file" - - echo -e "$script_content" -} - -add_launch_options() { - local settings_file="$1" - local script_content="$2" - local launch_options=$(grep "^launch_options=" "$settings_file" 2>/dev/null | cut -d'=' -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - - script_content="${script_content}\n# Handle launch options if present\n" - if [ -n "$launch_options" ]; then - script_content="${script_content}# Execute the specified program with environment variables\n" - script_content="${script_content}${launch_options} \"\$@\"\n" - else - script_content="${script_content}# Launch the specified program with the environment variables\n" - script_content="${script_content}\"\$@\"\n" - fi - - echo -e "$script_content" -} - -create_gpu_script() { - local script_content="$1" - local volt_script="/usr/local/bin/volt" - - echo -e "$script_content" > "$volt_script" 2>/dev/null - chmod 755 "$volt_script" 2>/dev/null -} - -manage_gpu() { - local settings_file="$1" - - local script_content=$(read_gpu_settings "$settings_file") - script_content=$(add_launch_options "$settings_file" "$script_content") - - create_gpu_script "$script_content" -} - -parse_arguments() { - while [ $# -gt 0 ]; do - case "$1" in - -c|--cpu) - shift - local cpu_args=() - while [ $# -gt 0 ] && [[ "$1" != -* ]]; do - cpu_args+=("$1") - shift - done - manage_cpu "${cpu_args[@]}" - ;; - -d|--disk) - shift - local disk_args=() - while [ $# -gt 0 ] && [[ "$1" != -* ]]; do - disk_args+=("$1") - shift - done - manage_disk "${disk_args[@]}" - ;; - -k|--kernel) - shift - local kernel_args=() - while [ $# -gt 0 ] && [[ "$1" != -* ]]; do - kernel_args+=("$1") - shift - done - manage_kernel "${kernel_args[@]}" - ;; - -g|--gpu) - manage_gpu "$2" - shift 2 - ;; - *) - shift - ;; - esac - done -} - -parse_arguments "$@" diff --git a/src/config.py b/src/config.py index 083fee1..a10e8f9 100644 --- a/src/config.py +++ b/src/config.py @@ -44,15 +44,15 @@ def save_config(cpu_widgets, gpu_widgets, kernel_widgets, disk_widgets, profile_ cpu_config = {} for setting_key in CPUManager.CPU_SETTINGS.keys(): - if setting_key in cpu_widgets and hasattr(cpu_widgets[setting_key], 'currentText'): + if setting_key in cpu_widgets and hasattr(cpu_widgets[setting_key], "currentText"): cpu_config[setting_key] = cpu_widgets[setting_key].currentText() if cpu_config: - config['CPU'] = cpu_config + config["CPU"] = cpu_config gpu_config = {} for setting_key in GPULaunchManager.GPU_SETTINGS.keys(): for category_name, category_widgets in gpu_widgets.items(): - if category_name != 'LaunchOptions' and setting_key in category_widgets: + if category_name != "LaunchOptions" and setting_key in category_widgets: widget = category_widgets[setting_key] if isinstance(widget, QComboBox): gpu_config[setting_key] = widget.currentText() @@ -60,31 +60,31 @@ def save_config(cpu_widgets, gpu_widgets, kernel_widgets, disk_widgets, profile_ gpu_config[setting_key] = widget.text() break if gpu_config: - config['GPU'] = gpu_config + config["GPU"] = gpu_config - if 'LaunchOptions' in gpu_widgets and 'launch_options_input' in gpu_widgets['LaunchOptions']: - launch_options = gpu_widgets['LaunchOptions']['launch_options_input'].text().replace('%', '%%') - config['LaunchOptions'] = {'launch_options': launch_options} + if "LaunchOptions" in gpu_widgets and "launch_options_input" in gpu_widgets["LaunchOptions"]: + launch_options = gpu_widgets["LaunchOptions"]["launch_options_input"].text().replace("%", "%%") + config["LaunchOptions"] = {"launch_options": launch_options} kernel_config = {} for setting_key in KernelManager.KERNEL_SETTINGS.keys(): - widget_key = f'{setting_key}_input' + widget_key = f"{setting_key}_input" if widget_key in kernel_widgets: value = kernel_widgets[widget_key].text().strip() if value: kernel_config[setting_key] = value if kernel_config: - config['Kernel'] = kernel_config + config["Kernel"] = kernel_config disk_config = {} - for disk_name, disk_widgets_dict in disk_widgets['disk_settings'].items(): + for disk_name, disk_widgets_dict in disk_widgets["disk_settings"].items(): for setting_key in DiskManager.DISK_SETTINGS.keys(): if setting_key in disk_widgets_dict: disk_config[f"{disk_name}_{setting_key}"] = disk_widgets_dict[setting_key].currentText() if disk_config: - config['Disk'] = disk_config + config["Disk"] = disk_config - with open(ConfigManager.get_config_path(profile_name), 'w') as configfile: + with open(ConfigManager.get_config_path(profile_name), "w") as configfile: config.write(configfile) @staticmethod @@ -100,41 +100,41 @@ def load_config(cpu_widgets, gpu_widgets, kernel_widgets, disk_widgets, profile_ config.read(config_path) - if 'CPU' in config: + if "CPU" in config: for setting_key in CPUManager.CPU_SETTINGS.keys(): - if setting_key in config['CPU'] and setting_key in cpu_widgets: - cpu_widgets[setting_key].setCurrentText(config['CPU'][setting_key]) + if setting_key in config["CPU"] and setting_key in cpu_widgets: + cpu_widgets[setting_key].setCurrentText(config["CPU"][setting_key]) - if 'GPU' in config: + if "GPU" in config: for setting_key in GPULaunchManager.GPU_SETTINGS.keys(): - if setting_key in config['GPU']: + if setting_key in config["GPU"]: for category_name, category_widgets in gpu_widgets.items(): - if category_name != 'LaunchOptions' and setting_key in category_widgets: + if category_name != "LaunchOptions" and setting_key in category_widgets: widget = category_widgets[setting_key] - value = config['GPU'][setting_key] + value = config["GPU"][setting_key] if isinstance(widget, QComboBox): widget.setCurrentText(value) elif isinstance(widget, QLineEdit): widget.setText(value) break - if 'LaunchOptions' in config and 'LaunchOptions' in gpu_widgets and 'launch_options_input' in gpu_widgets['LaunchOptions']: - launch_options = config['LaunchOptions'].get('launch_options', '').replace('%%', '%') - gpu_widgets['LaunchOptions']['launch_options_input'].setText(launch_options) + if "LaunchOptions" in config and "LaunchOptions" in gpu_widgets and "launch_options_input" in gpu_widgets["LaunchOptions"]: + launch_options = config["LaunchOptions"].get("launch_options", "").replace("%%", "%") + gpu_widgets["LaunchOptions"]["launch_options_input"].setText(launch_options) - if kernel_widgets and 'Kernel' in config: + if kernel_widgets and "Kernel" in config: for setting_key in KernelManager.KERNEL_SETTINGS.keys(): - if setting_key in config['Kernel']: - widget_key = f'{setting_key}_input' + if setting_key in config["Kernel"]: + widget_key = f"{setting_key}_input" if widget_key in kernel_widgets: - kernel_widgets[widget_key].setText(config['Kernel'][setting_key]) - - if disk_widgets and 'disk_settings' in disk_widgets and 'Disk' in config: - for config_key, value in config['Disk'].items(): - if '_' in config_key: - disk_name, setting_key = config_key.rsplit('_', 1) - if disk_name in disk_widgets['disk_settings'] and setting_key in disk_widgets['disk_settings'][disk_name]: - disk_widgets['disk_settings'][disk_name][setting_key].setCurrentText(value) + kernel_widgets[widget_key].setText(config["Kernel"][setting_key]) + + if disk_widgets and "disk_settings" in disk_widgets and "Disk" in config: + for config_key, value in config["Disk"].items(): + if "_" in config_key: + disk_name, setting_key = config_key.rsplit("_", 1) + if disk_name in disk_widgets["disk_settings"] and setting_key in disk_widgets["disk_settings"][disk_name]: + disk_widgets["disk_settings"][disk_name][setting_key].setCurrentText(value) return True diff --git a/src/cpu.py b/src/cpu.py index 99b1b2a..b732a8d 100644 --- a/src/cpu.py +++ b/src/cpu.py @@ -7,42 +7,42 @@ class CPUManager: CPU_SETTINGS_CATEGORIES = { "Frequency": { - 'scaling_governor': { - 'label': "Governor:", - 'text': "Controls CPU frequency scaling policy to balance performance and power consumption.", - 'items': ["unset"], - 'path': "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", - 'available_path': "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors", - 'is_dynamic': True + "scaling_governor": { + "label": "Governor:", + "text": "Controls CPU frequency scaling policy to balance performance and power consumption.", + "items": ["unset"], + "path": "/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", + "available_path": "/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors", + "is_dynamic": True }, - 'scaling_max_freq': { - 'label': "Max Frequency (MHz):", - 'text': "Upper limit for CPU frequency. Higher values increase performance but consume more power.", - 'items': ["unset"], - 'path': "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", - 'min_path': "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq", - 'max_path': "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", - 'is_dynamic': False, - 'convert_to_mhz': True + "scaling_max_freq": { + "label": "Max Frequency (MHz):", + "text": "Upper limit for CPU frequency. Higher values increase performance but consume more power.", + "items": ["unset"], + "path": "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", + "min_path": "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq", + "max_path": "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", + "is_dynamic": False, + "convert_to_mhz": True }, - 'scaling_min_freq': { - 'label': "Min Frequency (MHz):", - 'text': "Lower limit for CPU frequency. Higher values reduce latency but prevent deep power saving.", - 'items': ["unset"], - 'path': "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", - 'min_path': "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq", - 'max_path': "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", - 'is_dynamic': False, - 'convert_to_mhz': True + "scaling_min_freq": { + "label": "Min Frequency (MHz):", + "text": "Lower limit for CPU frequency. Higher values reduce latency but prevent deep power saving.", + "items": ["unset"], + "path": "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", + "min_path": "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq", + "max_path": "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", + "is_dynamic": False, + "convert_to_mhz": True } }, "Scheduler": { - 'scheduler': { - 'label': "Pluggable Scheduler:", - 'text': "BPF-based schedulers (sched_ext) for workload-specific optimizations like latency, throughput, or power efficiency.", - 'items': ["unset", "none"], - 'search_paths': ["/usr/bin/", "/usr/local/bin/"], - 'is_dynamic': True + "scheduler": { + "label": "Pluggable Scheduler:", + "text": "BPF-based schedulers (sched_ext) for workload-specific optimizations like latency, throughput, or power efficiency.", + "items": ["unset", "none"], + "search_paths": ["/usr/bin/", "/usr/local/bin/"], + "is_dynamic": True } } } @@ -68,21 +68,21 @@ def get_current_value(setting_info): """ Get the current value for a CPU setting. """ - if 'path' not in setting_info: + if "path" not in setting_info: return None try: - with open(setting_info['path'], "r") as f: + with open(setting_info["path"], "r") as f: value = f.read().strip() - if setting_info.get('convert_to_mhz', False): + if setting_info.get("convert_to_mhz", False): try: value = str(int(value) // 1000) except ValueError: pass - if setting_info.get('is_dynamic', False): - match = re.search(r'\[([^\]]+)\]', value) + if setting_info.get("is_dynamic", False): + match = re.search(r"\[([^\]]+)\]", value) if match: return match.group(1) else: @@ -97,19 +97,19 @@ def get_available_values(setting_info): """ Get available values for a CPU setting. """ - base_items = setting_info.get('items', ["unset"]).copy() + base_items = setting_info.get("items", ["unset"]).copy() - if setting_info.get('is_dynamic', False): - if 'available_path' in setting_info: + if setting_info.get("is_dynamic", False): + if "available_path" in setting_info: try: - with open(setting_info['available_path'], "r") as f: + with open(setting_info["available_path"], "r") as f: available_values = f.read().strip().split() return base_items + [item for item in available_values if item not in base_items] except Exception: return base_items - elif 'search_paths' in setting_info: + elif "search_paths" in setting_info: schedulers = base_items.copy() - for search_path in setting_info['search_paths']: + for search_path in setting_info["search_paths"]: try: scx_files = glob.glob(os.path.join(search_path, "scx_*")) for file_path in scx_files: @@ -121,9 +121,9 @@ def get_available_values(setting_info): return schedulers else: try: - with open(setting_info['min_path'], "r") as f: + with open(setting_info["min_path"], "r") as f: min_freq = int(f.read().strip()) // 1000 - with open(setting_info['max_path'], "r") as f: + with open(setting_info["max_path"], "r") as f: max_freq = int(f.read().strip()) // 1000 freq_values = [str(f) for f in range(min_freq, max_freq + 100, 100)] return base_items + freq_values @@ -143,7 +143,9 @@ def get_current_scheduler(): process.start("ps", ["-eo", "comm"]) if process.waitForFinished(10000): - output = process.readAllStandardOutput().data().decode() + stdout = process.readAllStandardOutput().data().decode() + stderr = process.readAllStandardError().data().decode() + output = stdout + stderr processes = output.strip().splitlines() return next((p.strip() for p in processes if p.strip().startswith("scx_")), "none") return "none" @@ -174,28 +176,28 @@ def create_cpu_tab(): for category_name, category_settings in CPUManager.CPU_SETTINGS_CATEGORIES.items(): for setting_key, setting_info in category_settings.items(): layout = QHBoxLayout() - label = QLabel(setting_info['label']) + label = QLabel(setting_info["label"]) label.setWordWrap(True) label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) widgets[setting_key] = QComboBox() is_accessible = True - if 'path' in setting_info: - is_accessible = CPUManager.get_available_setting(setting_info['path']) + if "path" in setting_info: + is_accessible = CPUManager.get_available_setting(setting_info["path"]) available_values = CPUManager.get_available_values(setting_info) - if setting_key == 'scaling_max_freq' and not setting_info.get('is_dynamic', False): + if setting_key == "scaling_max_freq" and not setting_info.get("is_dynamic", False): available_values = list(reversed(available_values)) widgets[setting_key].addItems(available_values) if is_accessible: - widgets[setting_key].setToolTip(setting_info['text']) + widgets[setting_key].setToolTip(setting_info["text"]) else: widgets[setting_key].setEnabled(False) - if 'path' in setting_info: + if "path" in setting_info: widgets[setting_key].setToolTip(f"Setting file not available - {setting_info['label']} selection disabled") else: widgets[setting_key].setToolTip(f"SCX schedulers not available - {setting_info['label']} selection disabled") @@ -210,7 +212,7 @@ def create_cpu_tab(): current_value_label = QLabel("Updating...") current_value_label.setContentsMargins(0, 0, 0, 10) scroll_layout.addWidget(current_value_label) - widgets[f'current_{setting_key}_value'] = current_value_label + widgets[f"current_{setting_key}_value"] = current_value_label scroll_layout.addStretch(1) scroll_area.setWidget(scroll_widget) @@ -218,9 +220,9 @@ def create_cpu_tab(): CPUManager.create_cpu_apply_button(main_layout, widgets) - widgets['cpu_settings_applied'] = False - widgets['is_process_running'] = False - widgets['process'] = None + widgets["cpu_settings_applied"] = False + widgets["is_process_running"] = False + widgets["process"] = None return cpu_tab, widgets @@ -234,12 +236,12 @@ def create_cpu_apply_button(parent_layout, widgets): button_layout = QHBoxLayout(button_container) button_layout.setContentsMargins(11, 10, 11, 0) - widgets['cpu_apply_button'] = QPushButton("Apply") - widgets['cpu_apply_button'].setMinimumSize(100, 30) - widgets['cpu_apply_button'].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + widgets["cpu_apply_button"] = QPushButton("Apply") + widgets["cpu_apply_button"].setMinimumSize(100, 30) + widgets["cpu_apply_button"].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button_layout.addStretch(1) - button_layout.addWidget(widgets['cpu_apply_button']) + button_layout.addWidget(widgets["cpu_apply_button"]) button_layout.addStretch(1) parent_layout.addWidget(button_container) @@ -251,11 +253,11 @@ def refresh_cpu_values(widgets): Refresh the current CPU values displayed in the interface. """ for setting_key, setting_info in CPUManager.CPU_SETTINGS.items(): - current_value_label = widgets.get(f'current_{setting_key}_value') + current_value_label = widgets.get(f"current_{setting_key}_value") if not current_value_label: continue - if setting_key == 'scheduler': + if setting_key == "scheduler": current_value_label.setText("Updating...") try: running_scheduler = CPUManager.get_current_scheduler() @@ -269,7 +271,7 @@ def refresh_cpu_values(widgets): except Exception: current_value_label.setText("current: Error") else: - is_accessible = CPUManager.get_available_setting(setting_info['path']) + is_accessible = CPUManager.get_available_setting(setting_info["path"]) if not is_accessible: current_value_label.setText("current: unset") else: diff --git a/src/disk.py b/src/disk.py index 9b3216a..af6bea9 100644 --- a/src/disk.py +++ b/src/disk.py @@ -8,10 +8,10 @@ class DiskManager: DISK_SETTINGS_CATEGORIES = { "Scheduler": { - 'scheduler': { - 'label': "Scheduler:", - 'items': ["unset"], - 'text': "Determines how disk I/O requests are scheduled and merged to balance throughput and latency." + "scheduler": { + "label": "Scheduler:", + "items": ["unset"], + "text": "Determines how disk I/O requests are scheduled and merged to balance throughput and latency." } } } @@ -29,13 +29,13 @@ def get_schedulers(): try: scheduler_files = glob.glob(DiskManager.DISK_SCHEDULER_PATH_PATTERN) for file_path in scheduler_files: - disk_name = file_path.split('/')[-3] + disk_name = file_path.split("/")[-3] try: - with open(file_path, 'r') as f: + with open(file_path, "r") as f: content = f.read().strip() scheduler_info = DiskManager.parse_scheduler_content(content) if scheduler_info: - scheduler_info['path'] = file_path + scheduler_info["path"] = file_path disk_info[disk_name] = scheduler_info except Exception: continue @@ -56,9 +56,9 @@ def parse_scheduler_content(content): if not tokens: return None - available = DiskManager.DISK_SETTINGS_CATEGORIES["Scheduler"]['scheduler']['items'].copy() + available = DiskManager.DISK_SETTINGS_CATEGORIES["Scheduler"]["scheduler"]["items"].copy() current = None - bracket_pattern = re.compile(r'\[([^\]]+)\]') + bracket_pattern = re.compile(r"\[([^\]]+)\]") for token in tokens: bracket_match = bracket_pattern.search(token) @@ -83,7 +83,7 @@ def parse_scheduler_content(content): seen.add(scheduler) unique_available.append(scheduler) - return {'current': current, 'available': unique_available} + return {"current": current, "available": unique_available} except Exception: return None @@ -108,7 +108,7 @@ def create_disk_tab(): scroll_layout.setContentsMargins(10, 10, 10, 0) widgets = {} - widgets['disk_settings'] = {} + widgets["disk_settings"] = {} disk_info = DiskManager.get_schedulers() sorted_disk_names = sorted(disk_info.keys()) @@ -126,28 +126,28 @@ def create_disk_tab(): disk_widgets[setting_key] = QComboBox() - available_schedulers = scheduler_info['available'] - if setting_info['items'][0] in available_schedulers: - sorted_schedulers = [setting_info['items'][0]] + sorted([s for s in available_schedulers if s != setting_info['items'][0]]) + available_schedulers = scheduler_info["available"] + if setting_info["items"][0] in available_schedulers: + sorted_schedulers = [setting_info["items"][0]] + sorted([s for s in available_schedulers if s != setting_info["items"][0]]) else: sorted_schedulers = sorted(available_schedulers) disk_widgets[setting_key].addItems(sorted_schedulers) - disk_widgets[setting_key].setCurrentText(setting_info['items'][0]) + disk_widgets[setting_key].setCurrentText(setting_info["items"][0]) disk_widgets[setting_key].setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - disk_widgets[setting_key].setToolTip(setting_info['text']) + disk_widgets[setting_key].setToolTip(setting_info["text"]) layout.addWidget(label) layout.addWidget(disk_widgets[setting_key]) scroll_layout.addLayout(layout) - current_scheduler = scheduler_info['current'] + current_scheduler = scheduler_info["current"] current_value_label = QLabel(f"current: {current_scheduler}") current_value_label.setContentsMargins(0, 0, 0, 10) scroll_layout.addWidget(current_value_label) - disk_widgets[f'current_{setting_key}_value'] = current_value_label + disk_widgets[f"current_{setting_key}_value"] = current_value_label - widgets['disk_settings'][disk_name] = disk_widgets + widgets["disk_settings"][disk_name] = disk_widgets scroll_layout.addStretch(1) scroll_area.setWidget(scroll_widget) @@ -155,9 +155,9 @@ def create_disk_tab(): DiskManager.create_disk_apply_button(main_layout, widgets) - widgets['disk_settings_applied'] = False - widgets['is_process_running'] = False - widgets['process'] = None + widgets["disk_settings_applied"] = False + widgets["is_process_running"] = False + widgets["process"] = None return disk_tab, widgets @@ -171,12 +171,12 @@ def create_disk_apply_button(parent_layout, widgets): button_layout = QHBoxLayout(button_container) button_layout.setContentsMargins(11, 10, 11, 0) - widgets['disk_apply_button'] = QPushButton("Apply") - widgets['disk_apply_button'].setMinimumSize(100, 30) - widgets['disk_apply_button'].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + widgets["disk_apply_button"] = QPushButton("Apply") + widgets["disk_apply_button"].setMinimumSize(100, 30) + widgets["disk_apply_button"].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button_layout.addStretch(1) - button_layout.addWidget(widgets['disk_apply_button']) + button_layout.addWidget(widgets["disk_apply_button"]) button_layout.addStretch(1) parent_layout.addWidget(button_container) @@ -190,7 +190,7 @@ def refresh_disk_values(widgets): disk_info = DiskManager.get_schedulers() for disk_name, scheduler_info in disk_info.items(): - if disk_name in widgets['disk_settings']: - disk_widgets = widgets['disk_settings'][disk_name] - current_scheduler = scheduler_info['current'] - disk_widgets['current_scheduler_value'].setText(f"current: {current_scheduler}") + if disk_name in widgets["disk_settings"]: + disk_widgets = widgets["disk_settings"][disk_name] + current_scheduler = scheduler_info["current"] + disk_widgets["current_scheduler_value"].setText(f"current: {current_scheduler}") diff --git a/src/gpu_launch.py b/src/gpu_launch.py index a55c1e8..8b8fcc7 100644 --- a/src/gpu_launch.py +++ b/src/gpu_launch.py @@ -9,154 +9,154 @@ class GPULaunchManager: GPU_SETTINGS_CATEGORIES = { "Mesa": { - 'mesa_gl_vsync': { - 'label': "OpenGL Vsync:", - 'text': "OpenGL vertical synchronization.", - 'items': ["unset", "program decides (default)", "default interval 0", "default interval 1", "on", "off"], - 'env_mapping': { - 'var_names': ['vblank_mode'], - 'values': {'default interval 0': '1', 'default interval 1': '2', 'on': '3', 'off': '0'} + "mesa_gl_vsync": { + "label": "OpenGL Vsync:", + "text": "OpenGL vertical synchronization.", + "items": ["unset", "program decides (default)", "default interval 0", "default interval 1", "on", "off"], + "env_mapping": { + "var_names": ["vblank_mode"], + "values": {"default interval 0": "1", "default interval 1": "2", "on": "3", "off": "0"} } }, - 'mesa_gl_thread_opt': { - 'label': "OpenGL Thread Optimizations:", - 'text': "Multi-threaded OpenGL command processing. Might improve or worsen OpenGL performance depending on the program being run.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['mesa_glthread'], - 'values': {'on': 'true'} + "mesa_gl_thread_opt": { + "label": "OpenGL Thread Optimizations:", + "text": "Multi-threaded OpenGL command processing. Might improve or worsen OpenGL performance depending on the program being run.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["mesa_glthread"], + "values": {"on": "true"} } }, - 'mesa_gl_dither': { - 'label': "OpenGL Texture Dithering:", - 'text': "OpenGL color dithering on low-depth framebuffers. Reduces color banding on displays with limited color depth at minimal performance cost.", - 'items': ["unset", "on (default)", "off"], - 'env_mapping': { - 'var_names': ['MESA_NO_DITHER'], - 'values': {'off': '1'} + "mesa_gl_dither": { + "label": "OpenGL Texture Dithering:", + "text": "OpenGL color dithering on low-depth framebuffers. Reduces color banding on displays with limited color depth at minimal performance cost.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["MESA_NO_DITHER"], + "values": {"off": "1"} } }, - 'mesa_gl_msaa': { - 'label': "OpenGL MSAA:", - 'text': "Multisample anti-aliasing in OpenGL. Smooths jagged edges by sampling multiple points per pixel, improving image quality with performance impact.", - 'items': ["unset", "on (default)", 'off'], - 'env_mapping': { - 'var_names': ['DRI_NO_MSAA'], - 'values': {'off': '1'} + "mesa_gl_msaa": { + "label": "OpenGL MSAA:", + "text": "Multisample anti-aliasing in OpenGL. Smooths jagged edges by sampling multiple points per pixel, improving image quality with performance impact.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["DRI_NO_MSAA"], + "values": {"off": "1"} } }, - 'mesa_gl_error_check': { - 'label': "OpenGL Error Checking:", - 'text': "OpenGL error checking. Validates API calls for correctness; disable for performance in stable applications.", - 'items': ["unset", "on (default)", "off"], - 'env_mapping': { - 'var_names': ['MESA_NO_ERROR'], - 'values': {'off': '1'} + "mesa_gl_error_check": { + "label": "OpenGL Error Checking:", + "text": "OpenGL error checking. Validates API calls for correctness; disable for performance in stable applications.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["MESA_NO_ERROR"], + "values": {"off": "1"} } }, - 'mesa_gl_fake': { - 'label': "OpenGL Version Spoofing:", - 'text': "Report a different OpenGL version to applications. Useful for running games that check version numbers but don't need newer features.", - 'items': ["unset", "off (default)", "3.3", "3.3compat", "4.6", "4.6compat"], - 'env_mapping': { - 'var_names': ['MESA_GL_VERSION_OVERRIDE'], - 'direct_value': True + "mesa_gl_fake": { + "label": "OpenGL Version Spoofing:", + "text": "Report a different OpenGL version to applications. Useful for running games that check version numbers but don't need newer features.", + "items": ["unset", "off (default)", "3.3", "3.3compat", "4.6", "4.6compat"], + "env_mapping": { + "var_names": ["MESA_GL_VERSION_OVERRIDE"], + "direct_value": True } }, - 'mesa_glsl_fake': { - 'label': "GLSL Version Spoofing:", - 'text': "Report a different GLSL version to applications. Works with OpenGL version spoofing for compatibility workarounds.", - 'items': ["unset", "off (default)", "330", "460"], - 'env_mapping': { - 'var_names': ['MESA_GLSL_VERSION_OVERRIDE'], - 'direct_value': True + "mesa_glsl_fake": { + "label": "GLSL Version Spoofing:", + "text": "Report a different GLSL version to applications. Works with OpenGL version spoofing for compatibility workarounds.", + "items": ["unset", "off (default)", "330", "460"], + "env_mapping": { + "var_names": ["MESA_GLSL_VERSION_OVERRIDE"], + "direct_value": True } }, - 'mesa_vk_vsync': { - 'label': "Vulkan Vsync:", - 'text': "Vulkan vertical synchronization.", - 'items': ["unset", "program decides (default)", "mailbox", "adaptive vsync", "on", "off"], - 'env_mapping': { - 'var_names': ['MESA_VK_WSI_PRESENT_MODE'], - 'values': {'mailbox': 'mailbox', 'adaptive vsync': 'relaxed', 'on': 'fifo', 'off': 'immediate'} + "mesa_vk_vsync": { + "label": "Vulkan Vsync:", + "text": "Vulkan vertical synchronization.", + "items": ["unset", "program decides (default)", "mailbox", "adaptive vsync", "on", "off"], + "env_mapping": { + "var_names": ["MESA_VK_WSI_PRESENT_MODE"], + "values": {"mailbox": "mailbox", "adaptive vsync": "relaxed", "on": "fifo", "off": "immediate"} } }, - 'mesa_vk_submit_thread': { - 'label': "Vulkan Submit Thread:", - 'text': "Dedicated thread for Vulkan command submission. Separates command submission from command recording, might reduce CPU overhead.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['MESA_VK_ENABLE_SUBMIT_THREAD'], - 'values': {'on': '1'} + "mesa_vk_submit_thread": { + "label": "Vulkan Submit Thread:", + "text": "Dedicated thread for Vulkan command submission. Separates command submission from command recording, might reduce CPU overhead.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["MESA_VK_ENABLE_SUBMIT_THREAD"], + "values": {"on": "1"} } }, - 'mesa_vk_fake': { - 'label': "Vulkan Version Spoofing:", - 'text': "Report a different Vulkan version to applications. Bypasses version checks for games that artificially restrict compatibility.", - 'items': ["unset", "off (default)", "1.1", "1.2", "1.3", "1.4"], - 'env_mapping': { - 'var_names': ['MESA_VK_VERSION_OVERRIDE'], - 'direct_value': True + "mesa_vk_fake": { + "label": "Vulkan Version Spoofing:", + "text": "Report a different Vulkan version to applications. Bypasses version checks for games that artificially restrict compatibility.", + "items": ["unset", "off (default)", "1.1", "1.2", "1.3", "1.4"], + "env_mapping": { + "var_names": ["MESA_VK_VERSION_OVERRIDE"], + "direct_value": True } }, - 'mesa_shader_cache': { - 'label': "Shader Cache:", - 'text': "Disk-based shader caching. Stores compiled shaders to disk to eliminate compilation stuttering on subsequent launches.", - 'items': ["unset", "on (default)", "off"], - 'env_mapping': { - 'var_names': ['MESA_SHADER_CACHE_DISABLE', 'MESA_GLSL_CACHE_DISABLE'], - 'values': {'off': 'true'} + "mesa_shader_cache": { + "label": "Shader Cache:", + "text": "Disk-based shader caching. Stores compiled shaders to disk to eliminate compilation stuttering on subsequent launches.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["MESA_SHADER_CACHE_DISABLE", "MESA_GLSL_CACHE_DISABLE"], + "values": {"off": "true"} } }, - 'mesa_cache_size': { - 'label': "Shader Cache Size (GB):", - 'text': "Maximum size for the shader cache. Larger caches store more compiled shaders but consume more disk space.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(1, 11)] + [str(i) for i in [25, 50, 75, 100]], - 'env_mapping': { - 'var_names': ['MESA_SHADER_CACHE_MAX_SIZE', 'MESA_GLSL_CACHE_MAX_SIZE'], - 'direct_value': True + "mesa_cache_size": { + "label": "Shader Cache Size (GB):", + "text": "Maximum size for the shader cache. Larger caches store more compiled shaders but consume more disk space.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(1, 11)] + [str(i) for i in [25, 50, 75, 100]], + "env_mapping": { + "var_names": ["MESA_SHADER_CACHE_MAX_SIZE", "MESA_GLSL_CACHE_MAX_SIZE"], + "direct_value": True } }, - 'radeonsi_no_infinite_interp': { - 'label': "RadeonSI Disable Infinite Interpolation:", - 'text': "Disable infinite interpolation in RadeonSI. Workaround for rendering bugs in some games on AMD GPUs.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['radeonsi_no_infinite_interp'], - 'values': {'on': 'true'} + "radeonsi_no_infinite_interp": { + "label": "RadeonSI Disable Infinite Interpolation:", + "text": "Disable infinite interpolation in RadeonSI. Workaround for rendering bugs in some games on AMD GPUs.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["radeonsi_no_infinite_interp"], + "values": {"on": "true"} } }, - 'radeonsi_clamp_div_by_zero': { - 'label': "RadeonSI Clamp Division by Zero:", - 'text': "Clamp division by zero results in RadeonSI. Prevents crashes or visual glitches from shader math errors on AMD GPUs.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['radeonsi_clamp_div_by_zero'], - 'values': {'on': 'true'} + "radeonsi_clamp_div_by_zero": { + "label": "RadeonSI Clamp Division by Zero:", + "text": "Clamp division by zero results in RadeonSI. Prevents crashes or visual glitches from shader math errors on AMD GPUs.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["radeonsi_clamp_div_by_zero"], + "values": {"on": "true"} } }, - 'radeonsi_zerovram': { - 'label': "RadeonSI Clear VRAM to Zero:", - 'text': "Clear all allocated VRAM to zero before usage in RadeonSI. Might fix rendering corruptions on AMD GPUs.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['radeonsi_zerovram'], - 'values': {'on': 'true'} + "radeonsi_zerovram": { + "label": "RadeonSI Clear VRAM to Zero:", + "text": "Clear all allocated VRAM to zero before usage in RadeonSI. Might fix rendering corruptions on AMD GPUs.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["radeonsi_zerovram"], + "values": {"on": "true"} } }, - 'radv_anisotropic_filtering': { - 'label': "RADV Anisotropic Filtering:", - 'text': "Anisotropic filtering level for RADV. Improves texture quality at oblique angles with minimal performance impact on modern AMD GPUs.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(0, 17)], - 'env_mapping': { - 'var_names': ['RADV_TEX_ANISO'], - 'direct_value': True + "radv_anisotropic_filtering": { + "label": "RADV Anisotropic Filtering:", + "text": "Anisotropic filtering level for RADV. Improves texture quality at oblique angles with minimal performance impact on modern AMD GPUs.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(0, 17)], + "env_mapping": { + "var_names": ["RADV_TEX_ANISO"], + "direct_value": True } }, - 'radv_profile_pstate': { - 'label': "RADV Profile Pstate:", - 'text': "Performance state profiling in RADV. Forces specific GPU clock levels for consistent performance or power testing on AMD GPUs.", - 'items': [ + "radv_profile_pstate": { + "label": "RADV Profile Pstate:", + "text": "Performance state profiling in RADV. Forces specific GPU clock levels for consistent performance or power testing on AMD GPUs.", + "items": [ "unset", "program decides (default)", "gpu clocks on arbitrary level", @@ -164,415 +164,415 @@ class GPULaunchManager: "minimum memory clock", "maximum gpu clocks" ], - 'env_mapping': { - 'var_names': ['RADV_PROFILE_PSTATE'], - 'values': { - 'gpu clocks on arbitrary level': 'standard', - 'minimum shader clock': 'min_sclk', - 'minimum memory clock': 'min_mclk', - 'maximum gpu clocks': 'peak' + "env_mapping": { + "var_names": ["RADV_PROFILE_PSTATE"], + "values": { + "gpu clocks on arbitrary level": "standard", + "minimum shader clock": "min_sclk", + "minimum memory clock": "min_mclk", + "maximum gpu clocks": "peak" } } }, - 'radv_vrs': { - 'label': "RADV Variable Rate Shading (GFX10.3+):", - 'text': "Variable rate shading in RADV (GFX10.3+). Renders different screen areas at different resolutions to improve performance with minimal quality loss.", - 'items': ["unset", "program decides (default)", "2x2", "1x2", "2x1", "1x1"], - 'env_mapping': { - 'var_names': ['RADV_FORCE_VRS'], - 'direct_value': True + "radv_vrs": { + "label": "RADV Variable Rate Shading (GFX10.3+):", + "text": "Variable rate shading in RADV (GFX10.3+). Renders different screen areas at different resolutions to improve performance with minimal quality loss.", + "items": ["unset", "program decides (default)", "2x2", "1x2", "2x1", "1x1"], + "env_mapping": { + "var_names": ["RADV_FORCE_VRS"], + "direct_value": True } }, - 'intel_precise_trig': { - 'label': "Intel Driver Preference on Trigonometric Functions:", - 'text': "Precision vs performance tradeoff for trigonometric functions on Intel GPUs. Accuracy mode ensures correct results; performance mode may have minor errors but runs faster.", - 'items': ["unset", "accuracy", "performance (default)"], - 'env_mapping': { - 'var_names': ['INTEL_PRECISE_TRIG'], - 'values': {'accuracy': 'true'} + "intel_precise_trig": { + "label": "Intel Driver Preference on Trigonometric Functions:", + "text": "Precision vs performance tradeoff for trigonometric functions on Intel GPUs. Accuracy mode ensures correct results; performance mode may have minor errors but runs faster.", + "items": ["unset", "accuracy", "performance (default)"], + "env_mapping": { + "var_names": ["INTEL_PRECISE_TRIG"], + "values": {"accuracy": "true"} } }, - 'hasvk_always_bindless': { - 'label': "HASVK Bindless Descriptors:", - 'text': "Bindless descriptors in HASVK. Modern descriptor management technique that can improve performance on Intel GPUs.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['HASVK_ALWAYS_BINDLESS'], - 'values': {'on': 'true'} + "hasvk_always_bindless": { + "label": "HASVK Bindless Descriptors:", + "text": "Bindless descriptors in HASVK. Modern descriptor management technique that can improve performance on Intel GPUs.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["HASVK_ALWAYS_BINDLESS"], + "values": {"on": "true"} } }, - 'hasvk_userspace_relocs': { - 'label': "HASVK Userspace Relocations:", - 'text': "Userspace relocations in HASVK. Handles GPU memory address patching in userspace instead of kernel for reduced overhead on older Intel GPUs.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['HASVK_USERSPACE_RELOCS'], - 'values': {'on': 'true'} + "hasvk_userspace_relocs": { + "label": "HASVK Userspace Relocations:", + "text": "Userspace relocations in HASVK. Handles GPU memory address patching in userspace instead of kernel for reduced overhead on older Intel GPUs.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["HASVK_USERSPACE_RELOCS"], + "values": {"on": "true"} } }, - 'anv_sparse': { - 'label': "ANV Sparse Resources (Tiger Lake+):", - 'text': "Sparse resources in ANV (Tiger Lake+). Allows partial allocation of large textures to save memory.", - 'items': ["unset", "on (default)", "off"], - 'env_mapping': { - 'var_names': ['ANV_SPARSE'], - 'values': {'off': 'false'} + "anv_sparse": { + "label": "ANV Sparse Resources (Tiger Lake+):", + "text": "Sparse resources in ANV (Tiger Lake+). Allows partial allocation of large textures to save memory.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["ANV_SPARSE"], + "values": {"off": "false"} } }, - 'anv_sparse_implementation': { - 'label': "ANV Sparse Implementation (Lunar Lake+):", - 'text': "Sparse resource implementation in ANV (Lunar Lake+). TRTT is the older method, Xe is the newer hardware-accelerated approach.", - 'items': ["unset", "TRTT", "Xe (default)"], - 'env_mapping': { - 'var_names': ['ANV_SPARSE_USE_TRTT'], - 'values': {'TRTT': 'true'} + "anv_sparse_implementation": { + "label": "ANV Sparse Implementation (Lunar Lake+):", + "text": "Sparse resource implementation in ANV (Lunar Lake+). TRTT is the older method, Xe is the newer hardware-accelerated approach.", + "items": ["unset", "TRTT", "Xe (default)"], + "env_mapping": { + "var_names": ["ANV_SPARSE_USE_TRTT"], + "values": {"TRTT": "true"} } }, - 'nvk_broken_driver': { - 'label': "NVK for Experimental/Untested GPUs:", - 'text': "Experimental NVK driver support for untested GPUs. Enables the open-source Vulkan driver on NVIDIA GPUs that lack official support; may be unstable.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['NVK_I_WANT_A_BROKEN_VULKAN_DRIVER'], - 'values': {'on': 'true'} + "nvk_broken_driver": { + "label": "NVK for Experimental/Untested GPUs:", + "text": "Experimental NVK driver support for untested GPUs. Enables the open-source Vulkan driver on NVIDIA GPUs that lack official support; may be unstable.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["NVK_I_WANT_A_BROKEN_VULKAN_DRIVER"], + "values": {"on": "true"} } } }, "NVIDIA": { - 'nvidia_gl_vsync': { - 'label': "OpenGL Vsync:", - 'text': "OpenGL vertical synchronization.", - 'items': ["unset", "program decides (default)", "on", "off"], - 'env_mapping': { - 'var_names': ['__GL_SYNC_TO_VBLANK'], - 'values': {'on': '1', 'off': '0'} + "nvidia_gl_vsync": { + "label": "OpenGL Vsync:", + "text": "OpenGL vertical synchronization.", + "items": ["unset", "program decides (default)", "on", "off"], + "env_mapping": { + "var_names": ["__GL_SYNC_TO_VBLANK"], + "values": {"on": "1", "off": "0"} } }, - 'nvidia_gl_gsync': { - 'label': "OpenGL G-SYNC:", - 'text': "OpenGL G-SYNC/Variable Refresh Rate (VRR). Adaptive sync, that eliminates tearing without the latency penalty of fixed vsync.", - 'items': ["unset", "program decides (default)", "on", "off"], - 'env_mapping': { - 'var_names': ['__GL_VRR_ALLOWED', '__GL_GSYNC_ALLOWED'], - 'values': {'on': '1', 'off': '0'} + "nvidia_gl_gsync": { + "label": "OpenGL G-SYNC:", + "text": "OpenGL G-SYNC/Variable Refresh Rate (VRR). Adaptive sync, that eliminates tearing without the latency penalty of fixed vsync.", + "items": ["unset", "program decides (default)", "on", "off"], + "env_mapping": { + "var_names": ["__GL_VRR_ALLOWED", "__GL_GSYNC_ALLOWED"], + "values": {"on": "1", "off": "0"} } }, - 'nvidia_gl_thread_opt': { - 'label': "OpenGL Thread Optimizations:", - 'text': "Multi-threaded OpenGL command processing. Might improve or worsen OpenGL performance depending on the program being run.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['__GL_THREADED_OPTIMIZATIONS'], - 'values': {'on': '1'} + "nvidia_gl_thread_opt": { + "label": "OpenGL Thread Optimizations:", + "text": "Multi-threaded OpenGL command processing. Might improve or worsen OpenGL performance depending on the program being run.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["__GL_THREADED_OPTIMIZATIONS"], + "values": {"on": "1"} } }, - 'nvidia_gl_yield': { - 'label': "OpenGL Yield Behavior:", - 'text': "NVIDIA driver yields CPU time during OpenGL operations. Controls how the driver waits for GPU, affecting CPU usage and responsiveness.", - 'items': ["unset", "call sched_yield() (default)", "never yield", "call usleep(0) to yield"], - 'env_mapping': { - 'var_names': ['__GL_YIELD'], - 'values': {'never yield': 'NOTHING', 'call usleep(0) to yield': 'USLEEP'} + "nvidia_gl_yield": { + "label": "OpenGL Yield Behavior:", + "text": "NVIDIA driver yields CPU time during OpenGL operations. Controls how the driver waits for GPU, affecting CPU usage and responsiveness.", + "items": ["unset", "call sched_yield() (default)", "never yield", "call usleep(0) to yield"], + "env_mapping": { + "var_names": ["__GL_YIELD"], + "values": {"never yield": "NOTHING", "call usleep(0) to yield": "USLEEP"} } }, - 'nvidia_gl_texture_quality': { - 'label': "OpenGL Texture Quality:", - 'text': "Texture quality vs performance tradeoff in OpenGL. Quality uses better filtering, performance uses faster methods with potential visual degradation.", - 'items': ["unset", "program decides (default)", "quality", "mixed", "performance"], - 'env_mapping': { - 'var_names': ['__GL_OpenGLImageSettings'], - 'values': {'quality': '1', 'mixed': '2', 'performance': '3'} + "nvidia_gl_texture_quality": { + "label": "OpenGL Texture Quality:", + "text": "Texture quality vs performance tradeoff in OpenGL. Quality uses better filtering, performance uses faster methods with potential visual degradation.", + "items": ["unset", "program decides (default)", "quality", "mixed", "performance"], + "env_mapping": { + "var_names": ["__GL_OpenGLImageSettings"], + "values": {"quality": "1", "mixed": "2", "performance": "3"} } }, - 'nvidia_gl_fsaa': { - 'label': "OpenGL Full Scene Antialiasing:", - 'text': "Full scene anti-aliasing level in OpenGL. Reduces jagged edges using multisampling (ms) and coverage sampling (cs/ss) with significant performance cost at higher levels.", - 'items': [ + "nvidia_gl_fsaa": { + "label": "OpenGL Full Scene Antialiasing:", + "text": "Full scene anti-aliasing level in OpenGL. Reduces jagged edges using multisampling (ms) and coverage sampling (cs/ss) with significant performance cost at higher levels.", + "items": [ "unset", "program decides (default)", "0 - off", "1 - 2x (2xms)", "5 - 4x (4xms)", "7 - 8x (4xms, 4xcs)", "8 - 16x (4xms, 12xcs)", "9 - 8x (4xss, 2xms)", "10 - 8x (8xms)", "11 - 16x (4xss, 4xms)", "12 - 16x (8xms, 8xcs)", "14 - 32x (8xms, 24xcs)" ], - 'env_mapping': { - 'var_names': ['__GL_FSAA_MODE'], - 'extract_prefix': True + "env_mapping": { + "var_names": ["__GL_FSAA_MODE"], + "extract_prefix": True } }, - 'nvidia_gl_fxaa': { - 'label': "OpenGL FXAA:", - 'text': "Fast approximate anti-aliasing in OpenGL. Post-process AA that smooths edges with minimal performance cost but may blur textures. FXAA must first be enabled in NVIDIA Control Panel.", - 'items': ["unset", "on (default)", "off"], - 'env_mapping': { - 'var_names': ['__GL_ALLOW_FXAA_USAGE'], - 'values': {'off': '0'} + "nvidia_gl_fxaa": { + "label": "OpenGL FXAA:", + "text": "Fast approximate anti-aliasing in OpenGL. Post-process AA that smooths edges with minimal performance cost but may blur textures. FXAA must first be enabled in NVIDIA Control Panel.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["__GL_ALLOW_FXAA_USAGE"], + "values": {"off": "0"} } }, - 'nvidia_gl_anisotropic_filtering': { - 'label': "OpenGL Anisotropic Filtering:", - 'text': "Anisotropic filtering level in OpenGL. Improves texture sharpness at oblique viewing angles; higher levels look better but impact performance.", - 'items': [ + "nvidia_gl_anisotropic_filtering": { + "label": "OpenGL Anisotropic Filtering:", + "text": "Anisotropic filtering level in OpenGL. Improves texture sharpness at oblique viewing angles; higher levels look better but impact performance.", + "items": [ "unset", "program decides (default)", "0 - no anisotropic filtering", "1 - 2x anisotropic filtering", "2 - 4x anisotropic filtering", "3 - 8x anisotropic filtering", "4 - 16x anisotropic filtering" ], - 'env_mapping': { - 'var_names': ['__GL_LOG_MAX_ANISO'], - 'extract_prefix': True + "env_mapping": { + "var_names": ["__GL_LOG_MAX_ANISO"], + "extract_prefix": True } }, - 'nvidia_smooth_motion': { - 'label': "Smooth Motion (RTX 40 Series+):", - 'text': "Smooth motion feature (RTX 40 Series+). Optical flow frame interpolation that generates intermediate frames for smoother motion. Vulkan only.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['NVPRESENT_ENABLE_SMOOTH_MOTION'], - 'values': {'on': '1'} + "nvidia_smooth_motion": { + "label": "Smooth Motion (RTX 40 Series+):", + "text": "Smooth motion feature (RTX 40 Series+). Optical flow frame interpolation that generates intermediate frames for smoother motion. Vulkan only.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["NVPRESENT_ENABLE_SMOOTH_MOTION"], + "values": {"on": "1"} } }, - 'nvidia_max_prerendered_frames': { - 'label': "Maximum Pre-rendered Frames:", - 'text': "Maximum number of pre-rendered frames. Lower values reduce input lag but may hurt frame rate consistency; higher values do the opposite.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(1, 5)], - 'env_mapping': { - 'var_names': ['__GL_MaxFramesAllowed'], - 'direct_value': True + "nvidia_max_prerendered_frames": { + "label": "Maximum Pre-rendered Frames:", + "text": "Maximum number of pre-rendered frames. Lower values reduce input lag but may hurt frame rate consistency; higher values do the opposite.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(1, 5)], + "env_mapping": { + "var_names": ["__GL_MaxFramesAllowed"], + "direct_value": True } }, - 'nvidia_sharpen_denoising_enable': { - 'label': "Enable NVIDIA Image Sharpening and Denoising:", - 'text': "Enable NVIDIA Image Sharpening and Denoising. Post-processing filters that enhance image clarity and reduce noise artifacts.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['__GL_SHARPEN_ENABLE'], - 'values': {'on': '1'} + "nvidia_sharpen_denoising_enable": { + "label": "Enable NVIDIA Image Sharpening and Denoising:", + "text": "Enable NVIDIA Image Sharpening and Denoising. Post-processing filters that enhance image clarity and reduce noise artifacts.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["__GL_SHARPEN_ENABLE"], + "values": {"on": "1"} } }, - 'nvidia_image_sharpening': { - 'label': "Image Sharpening:", - 'text': "Image sharpening. Enhances texture and edge definition; higher values increase sharpness but may introduce artifacts.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(0, 101)], - 'env_mapping': { - 'var_names': ['__GL_SHARPEN_VALUE'], - 'direct_value': True + "nvidia_image_sharpening": { + "label": "Image Sharpening:", + "text": "Image sharpening. Enhances texture and edge definition; higher values increase sharpness but may introduce artifacts.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(0, 101)], + "env_mapping": { + "var_names": ["__GL_SHARPEN_VALUE"], + "direct_value": True } }, - 'nvidia_image_denoising': { - 'label': "Image Denoising", - 'text': "Image denoising. Reduces film grain and noise; higher values preserve more texture detail but may keep more noise.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(0, 101)], - 'env_mapping': { - 'var_names': ['__GL_SHARPEN_IGNORE_FILM_GRAIN'], - 'direct_value': True + "nvidia_image_denoising": { + "label": "Image Denoising", + "text": "Image denoising. Reduces film grain and noise; higher values preserve more texture detail but may keep more noise.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(0, 101)], + "env_mapping": { + "var_names": ["__GL_SHARPEN_IGNORE_FILM_GRAIN"], + "direct_value": True } }, - 'nvidia_shader_cache': { - 'label': "Shader Cache:", - 'text': "Disk-based shader caching. Stores compiled shaders to disk to eliminate compilation stuttering on subsequent launches.", - 'items': ["unset", "on (default)", "off"], - 'env_mapping': { - 'var_names': ['__GL_SHADER_DISK_CACHE'], - 'values': {'off': '0'} + "nvidia_shader_cache": { + "label": "Shader Cache:", + "text": "Disk-based shader caching. Stores compiled shaders to disk to eliminate compilation stuttering on subsequent launches.", + "items": ["unset", "on (default)", "off"], + "env_mapping": { + "var_names": ["__GL_SHADER_DISK_CACHE"], + "values": {"off": "0"} } }, - 'nvidia_shader_cache_size': { - 'label': "Shader Cache Size (GB):", - 'text': "Maximum size for the shader cache. Larger caches store more compiled shaders but consume more disk space.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(1, 11)] + [str(i) for i in [25, 50, 75, 100]], - 'env_mapping': { - 'var_names': ['__GL_SHADER_DISK_CACHE_SIZE'], - 'convert_to_bytes': True + "nvidia_shader_cache_size": { + "label": "Shader Cache Size (GB):", + "text": "Maximum size for the shader cache. Larger caches store more compiled shaders but consume more disk space.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(1, 11)] + [str(i) for i in [25, 50, 75, 100]], + "env_mapping": { + "var_names": ["__GL_SHADER_DISK_CACHE_SIZE"], + "convert_to_bytes": True } }, - 'nvidia_glsl_ext_requirements': { - 'label': "Ignore GLSL Extensions Requirements:", - 'text': "Ignore GLSL extension requirements. Allows GLSL shaders to compile without proper #extension directives or compatibility profile declarations. Fixes shader compilation errors in some games.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['__GL_IGNORE_GLSL_EXT_REQS'], - 'values': {'on': '1'} + "nvidia_glsl_ext_requirements": { + "label": "Ignore GLSL Extensions Requirements:", + "text": "Ignore GLSL extension requirements. Allows GLSL shaders to compile without proper #extension directives or compatibility profile declarations. Fixes shader compilation errors in some games.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["__GL_IGNORE_GLSL_EXT_REQS"], + "values": {"on": "1"} } }, - 'nvidia_glx_unofficial_protocol': { - 'label': "Unofficial GLX Protocol:", - 'text': "Unofficial GLX protocol. Enables extensions that aren't part of the official GLX specification.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['__GL_ALLOW_UNOFFICIAL_PROTOCOL'], - 'values': {'on': '1'} + "nvidia_glx_unofficial_protocol": { + "label": "Unofficial GLX Protocol:", + "text": "Unofficial GLX protocol. Enables extensions that aren't part of the official GLX specification.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["__GL_ALLOW_UNOFFICIAL_PROTOCOL"], + "values": {"on": "1"} } }, - 'nvidia_experimental_perf': { - 'label': "Experimental Performance Strategy:", - 'text': "Experimental GPU clock boost management. Allows the driver to more aggressively reduce GPU clocks after boost periods, potentially reducing power consumption.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['__GL_ExperimentalPerfStrategy'], - 'values': {'on': '1'} + "nvidia_experimental_perf": { + "label": "Experimental Performance Strategy:", + "text": "Experimental GPU clock boost management. Allows the driver to more aggressively reduce GPU clocks after boost periods, potentially reducing power consumption.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["__GL_ExperimentalPerfStrategy"], + "values": {"on": "1"} } } }, "RenderSelector": { - 'render_gl_device': { - 'label': "Select OpenGL Device:", - 'text': "OpenGL device to use.", - 'items': ["unset", "program decides (default)"] + "render_gl_device": { + "label": "Select OpenGL Device:", + "text": "OpenGL device to use.", + "items": ["unset", "program decides (default)"] }, - 'render_vk_device': { - 'label': "Select Vulkan Device:", - 'text': "Vulkan device to use.", - 'items': ["unset", "program decides (default)"] + "render_vk_device": { + "label": "Select Vulkan Device:", + "text": "Vulkan device to use.", + "items": ["unset", "program decides (default)"] } }, "MangoHud": { - 'mangohud_enable': { - 'label': "Enable MangoHud:", - 'text': "Enable MangoHud.", - 'items': ["unset", "on", "off (default)"] + "mangohud_enable": { + "label": "Enable MangoHud:", + "text": "Enable MangoHud.", + "items": ["unset", "on", "off (default)"] }, - 'mangohud_display': { - 'label': "Display Elements:", - 'text': "Elements displayed in MangoHud overlay.", - 'items': ["unset", "program decides (default)", "no hud", "fps only", "horizontal", "extended", "detailed"], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'values': {'no hud': '0', 'fps only': '1', 'horizontal': '2', 'extended': '3', 'detailed': '4'}, - 'prefix': 'preset=' + "mangohud_display": { + "label": "Display Elements:", + "text": "Elements displayed in MangoHud overlay.", + "items": ["unset", "program decides (default)", "no hud", "fps only", "horizontal", "extended", "detailed"], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "values": {"no hud": "0", "fps only": "1", "horizontal": "2", "extended": "3", "detailed": "4"}, + "prefix": "preset=" } }, - 'mangohud_gl_vsync': { - 'label': "OpenGL Vsync:", - 'text': "OpenGL vertical synchronization mode when using MangoHud.", - 'items': ["unset", "program decides (default)", "adaptive vsync", "on", "off"], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'values': {'adaptive vsync': '-1', 'on': '1', 'off': '0'}, - 'prefix': 'gl_vsync=' + "mangohud_gl_vsync": { + "label": "OpenGL Vsync:", + "text": "OpenGL vertical synchronization mode when using MangoHud.", + "items": ["unset", "program decides (default)", "adaptive vsync", "on", "off"], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "values": {"adaptive vsync": "-1", "on": "1", "off": "0"}, + "prefix": "gl_vsync=" } }, - 'mangohud_vk_vsync': { - 'label': "Vulkan Vsync:", - 'text': "Vulkan vertical synchronization mode when using MangoHud.", - 'items': ["unset", "program decides (default)", "mailbox", "adaptive vsync", "on", "off"], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'values': {'mailbox': '2', 'adaptive vsync': '0', 'on': '3', 'off': '1'}, - 'prefix': 'vsync=' + "mangohud_vk_vsync": { + "label": "Vulkan Vsync:", + "text": "Vulkan vertical synchronization mode when using MangoHud.", + "items": ["unset", "program decides (default)", "mailbox", "adaptive vsync", "on", "off"], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "values": {"mailbox": "2", "adaptive vsync": "0", "on": "3", "off": "1"}, + "prefix": "vsync=" } }, - 'mangohud_fps_limit': { - 'label': "Fps Limit:", - 'text': "FPS limit when using MangoHud.", - 'items': ["unset", "program decides (default)", "unlimited", "10", "15", "20", "24", "25", "30", "35", "40", "45", "48", "50", "55", "60", "70", "72", "75", "85", "90", "100", "110", "120", "144", "165", "180", "200", "240", "280", "300", "360", "480"], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'values': {'unlimited': '0'}, - 'direct_value': True, - 'prefix': 'fps_limit=' + "mangohud_fps_limit": { + "label": "Fps Limit:", + "text": "FPS limit when using MangoHud.", + "items": ["unset", "program decides (default)", "unlimited", "10", "15", "20", "24", "25", "30", "35", "40", "45", "48", "50", "55", "60", "70", "72", "75", "85", "90", "100", "110", "120", "144", "165", "180", "200", "240", "280", "300", "360", "480"], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "values": {"unlimited": "0"}, + "direct_value": True, + "prefix": "fps_limit=" } }, - 'mangohud_fps_method': { - 'label': "Fps Limit Method:", - 'text': "MangoHud FPS limiting implementation.", - 'items': ["unset", "program decides (default)", "early - smoothest frametimes", "late - lowest latency"], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'values': {'early - smoothest frametimes': 'early', 'late - lowest latency': 'late'}, - 'prefix': 'fps_limit_method=' + "mangohud_fps_method": { + "label": "Fps Limit Method:", + "text": "MangoHud FPS limiting implementation.", + "items": ["unset", "program decides (default)", "early - smoothest frametimes", "late - lowest latency"], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "values": {"early - smoothest frametimes": "early", "late - lowest latency": "late"}, + "prefix": "fps_limit_method=" } }, - 'mangohud_texture_filter': { - 'label': "Texture Filtering:", - 'text': "Texture filtering method when using MangoHud. Vulkan Only.", - 'items': ["unset", "program decides (default)", "bicubic", "retro", "trilinear"], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'values': {'bicubic': 'bicubic', 'retro': 'retro', 'trilinear': 'trilinear'} + "mangohud_texture_filter": { + "label": "Texture Filtering:", + "text": "Texture filtering method when using MangoHud. Vulkan Only.", + "items": ["unset", "program decides (default)", "bicubic", "retro", "trilinear"], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "values": {"bicubic": "bicubic", "retro": "retro", "trilinear": "trilinear"} } }, - 'mangohud_mipmap_lod_bias': { - 'label': "Mipmap LOD Bias:", - 'text': "Mipmap level-of-detail bias when using MangoHud. Negative values sharpen textures, positive values blur them; affects performance and visual quality. Vulkan Only.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(-16, 17)], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'direct_value': True, - 'prefix': 'picmip=' + "mangohud_mipmap_lod_bias": { + "label": "Mipmap LOD Bias:", + "text": "Mipmap level-of-detail bias when using MangoHud. Negative values sharpen textures, positive values blur them; affects performance and visual quality. Vulkan Only.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(-16, 17)], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "direct_value": True, + "prefix": "picmip=" } }, - 'mangohud_anisotropic_filtering': { - 'label': "Anisotropic Filtering:", - 'text': "Anisotropic filtering level when using MangoHud. Improves texture quality at angles; 16x is maximum quality with minimal modern GPU impact. Vulkan Only.", - 'items': ["unset", "program decides (default)"] + [str(i) for i in range(0, 17)], - 'env_mapping': { - 'var_names': ['MANGOHUD_CONFIG'], - 'direct_value': True, - 'prefix': 'af=' + "mangohud_anisotropic_filtering": { + "label": "Anisotropic Filtering:", + "text": "Anisotropic filtering level when using MangoHud. Improves texture quality at angles; 16x is maximum quality with minimal modern GPU impact. Vulkan Only.", + "items": ["unset", "program decides (default)"] + [str(i) for i in range(0, 17)], + "env_mapping": { + "var_names": ["MANGOHUD_CONFIG"], + "direct_value": True, + "prefix": "af=" } } }, "LSFrameGen": { - 'lsfg_enable': { - 'label': "Enable LSFG-VK:", - 'text': "Enable LSFG-VK. Vulkan Only.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['LSFG_LEGACY'], - 'values': {'on': '1'} + "lsfg_enable": { + "label": "Enable LSFG-VK:", + "text": "Enable LSFG-VK. Vulkan Only.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["LSFG_LEGACY"], + "values": {"on": "1"} } }, - 'lsfg_dll_path': { - 'label': "Lossless.dll Path:", - 'text': "Path to the Lossless Scaling frame generation DLL file (Lossless.dll).", - 'path': True, - 'env_mapping': { - 'var_names': ['LSFG_DLL_PATH'], - 'direct_value': True + "lsfg_dll_path": { + "label": "Lossless.dll Path:", + "text": "Path to the Lossless Scaling frame generation DLL file (Lossless.dll).", + "path": True, + "env_mapping": { + "var_names": ["LSFG_DLL_PATH"], + "direct_value": True } }, - 'lsfg_multiplier': { - 'label': "FPS Multiplier:", - 'text': "Frame generation multiplier. Generates additional frames between real frames.", - 'items': ["unset", "program decides (default)", "2", "3", "4"], - 'env_mapping': { - 'var_names': ['LSFG_MULTIPLIER'], - 'direct_value': True + "lsfg_multiplier": { + "label": "FPS Multiplier:", + "text": "Frame generation multiplier. Generates additional frames between real frames.", + "items": ["unset", "program decides (default)", "2", "3", "4"], + "env_mapping": { + "var_names": ["LSFG_MULTIPLIER"], + "direct_value": True } }, - 'lsfg_flow_scale': { - 'label': "Motion Estimation Quality:", - 'text': "Motion estimation quality. Lower values improve performance at the cost of quality.", - 'items': ["unset", "program decides (default)", "0.25", "0.50", "0.75", "1.0"], - 'env_mapping': { - 'var_names': ['LSFG_FLOW_SCALE'], - 'direct_value': True + "lsfg_flow_scale": { + "label": "Motion Estimation Quality:", + "text": "Motion estimation quality. Lower values improve performance at the cost of quality.", + "items": ["unset", "program decides (default)", "0.25", "0.50", "0.75", "1.0"], + "env_mapping": { + "var_names": ["LSFG_FLOW_SCALE"], + "direct_value": True } }, - 'lsfg_performance_mode': { - 'label': "Performance Mode:", - 'text': "Performance mode which reduces quality for higher frame rates.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['LSFG_PERFORMANCE_MODE'], - 'values': {'on': '1'} + "lsfg_performance_mode": { + "label": "Performance Mode:", + "text": "Performance mode which reduces quality for higher frame rates.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["LSFG_PERFORMANCE_MODE"], + "values": {"on": "1"} } }, - 'lsfg_hdr_mode': { - 'label': "HDR Mode:", - 'text': "HDR support in frame generation.", - 'items': ["unset", "on", "off (default)"], - 'env_mapping': { - 'var_names': ['LSFG_HDR_MODE'], - 'values': {'on': '1'} + "lsfg_hdr_mode": { + "label": "HDR Mode:", + "text": "HDR support in frame generation.", + "items": ["unset", "on", "off (default)"], + "env_mapping": { + "var_names": ["LSFG_HDR_MODE"], + "values": {"on": "1"} } }, - 'lsfg_vk_present_mode': { - 'label': "Vulkan Vsync:", - 'text': "Vulkan vertical synchronization mode when using lsfg-vk.", - 'items': ["unset", "program decides (default)", "mailbox", "adaptive vsync", "on", "off"], - 'env_mapping': { - 'var_names': ['LSFG_EXPERIMENTAL_PRESENT_MODE'], - 'values': {'mailbox': 'mailbox', 'adaptive vsync': 'relaxed', 'on': 'fifo', 'off': 'immediate'} + "lsfg_vk_present_mode": { + "label": "Vulkan Vsync:", + "text": "Vulkan vertical synchronization mode when using lsfg-vk.", + "items": ["unset", "program decides (default)", "mailbox", "adaptive vsync", "on", "off"], + "env_mapping": { + "var_names": ["LSFG_EXPERIMENTAL_PRESENT_MODE"], + "values": {"mailbox": "mailbox", "adaptive vsync": "relaxed", "on": "fifo", "off": "immediate"} } } } @@ -587,10 +587,10 @@ def truncate_name(name): """ Truncate device name at slash or parenthesis. """ - if '/' in name: - return name.split('/')[0].strip() - if '(' in name: - return name.split('(')[0].strip() + if "/" in name: + return name.split("/")[0].strip() + if "(" in name: + return name.split("(")[0].strip() return name @staticmethod @@ -614,7 +614,9 @@ def get_available(program_name, search_flatpak): process.start("flatpak", ["list"]) if process.waitForFinished(10000): - output = process.readAllStandardOutput().data().decode() + stdout = process.readAllStandardOutput().data().decode() + stderr = process.readAllStandardError().data().decode() + output = stdout + stderr if program_name.lower() in output.lower(): return True except Exception: @@ -686,10 +688,12 @@ def get_opengl_device_options(): process.start("glxinfo") if process.waitForFinished(10000): - output = process.readAllStandardOutput().data().decode() - for line in output.split('\n'): + stdout = process.readAllStandardOutput().data().decode() + stderr = process.readAllStandardError().data().decode() + output = stdout + stderr + for line in output.split("\n"): if "OpenGL renderer string:" in line: - device_name = line.split(':', 1)[1].strip() + device_name = line.split(":", 1)[1].strip() device_name = GPULaunchManager.truncate_name(device_name) device_name = device_name.lower() @@ -712,12 +716,14 @@ def get_opengl_device_options(): process.start("glxinfo") if process.waitForFinished(10000): - output = process.readAllStandardOutput().data().decode() + stdout = process.readAllStandardOutput().data().decode() + stderr = process.readAllStandardError().data().decode() + output = stdout + stderr renderer_found = False - for line in output.split('\n'): + for line in output.split("\n"): if "OpenGL renderer string:" in line: - device_name = line.split(':', 1)[1].strip() + device_name = line.split(":", 1)[1].strip() device_name = GPULaunchManager.truncate_name(device_name) device_name = device_name.lower() @@ -775,30 +781,30 @@ def get_vulkan_device_options(): process.start("vulkaninfo") if process.waitForFinished(10000): - output = process.readAllStandardOutput().data().decode() - lines = output.split('\n') + stdout = process.readAllStandardOutput().data().decode() + stderr = process.readAllStandardError().data().decode() + output = stdout + stderr + lines = output.split("\n") current_device = {} for line in lines: line = line.strip() - if 'vendorID' in line and '=' in line: - vendor_id = line.split('=')[1].strip() - current_device['vendorID'] = vendor_id - elif 'deviceID' in line and '=' in line: - device_id = line.split('=')[1].strip() - current_device['deviceID'] = device_id - elif 'deviceName' in line and '=' in line: - device_name = line.split('=')[1].strip() - current_device['deviceName'] = device_name - - if all(key in current_device for key in ['vendorID', 'deviceID', 'deviceName']): - truncated_name = GPULaunchManager.truncate_name(current_device['deviceName']) + if "vendorID" in line and "=" in line: + vendor_id = line.split("=")[1].strip() + current_device["vendorID"] = vendor_id + elif "deviceID" in line and "=" in line: + device_id = line.split("=")[1].strip() + current_device["deviceID"] = device_id + elif "deviceName" in line and "=" in line: + device_name = line.split("=")[1].strip() + current_device["deviceName"] = device_name + + if all(key in current_device for key in ["vendorID", "deviceID", "deviceName"]): + truncated_name = GPULaunchManager.truncate_name(current_device["deviceName"]) display_name = truncated_name.lower() - if 'llvmpipe' in display_name: - display_name = 'llvmpipe (software rendering)' - else: - display_name = truncated_name.lower() + if "llvmpipe" in display_name: + display_name = "llvmpipe (software rendering)" device_key = f"{current_device['vendorID']}:{current_device['deviceID']}" devices.append(display_name) @@ -835,11 +841,11 @@ def create_gpu_settings_tabs(): gpu_layout.addWidget(gpu_subtabs) widgets = { - 'Mesa': mesa_widgets, - 'NVIDIA': nvidia_widgets, - 'RenderSelector': render_selector_widgets, - 'MangoHud': mangohud_widgets, - 'LSFrameGen': ls_frame_gen_widgets + "Mesa": mesa_widgets, + "NVIDIA": nvidia_widgets, + "RenderSelector": render_selector_widgets, + "MangoHud": mangohud_widgets, + "LSFrameGen": ls_frame_gen_widgets } return gpu_tab, widgets @@ -857,7 +863,7 @@ def create_path_widget(setting_info): path_input = QLineEdit() path_input.setPlaceholderText("No file selected") path_input.setReadOnly(True) - path_input.setToolTip(setting_info['text']) + path_input.setToolTip(setting_info["text"]) layout.addWidget(path_input) button_layout = QHBoxLayout() @@ -913,11 +919,11 @@ def create_category_tab(category_name): for setting_key, setting_info in GPULaunchManager.GPU_SETTINGS_CATEGORIES[category_name].items(): layout = QHBoxLayout() - label = QLabel(setting_info['label']) + label = QLabel(setting_info["label"]) label.setWordWrap(True) label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) - if setting_info.get('path', False): + if setting_info.get("path", False): path_widget, path_input, browse_button, clear_button = GPULaunchManager.create_path_widget(setting_info) widgets[setting_key] = path_input widgets[f"{setting_key}_browse"] = browse_button @@ -926,10 +932,10 @@ def create_category_tab(category_name): layout.addWidget(path_widget) else: widgets[setting_key] = QComboBox() - widgets[setting_key].addItems(setting_info['items']) + widgets[setting_key].addItems(setting_info["items"]) widgets[setting_key].setCurrentText("unset") widgets[setting_key].setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - widgets[setting_key].setToolTip(setting_info['text']) + widgets[setting_key].setToolTip(setting_info["text"]) layout.addWidget(label) layout.addWidget(widgets[setting_key]) @@ -938,37 +944,37 @@ def create_category_tab(category_name): if category_name == "RenderSelector": if GPULaunchManager.get_available_glxinfo(): opengl_devices, device_map = GPULaunchManager.get_opengl_device_options() - default_items = GPULaunchManager.GPU_SETTINGS_CATEGORIES["RenderSelector"]['render_gl_device']['items'] + default_items = GPULaunchManager.GPU_SETTINGS_CATEGORIES["RenderSelector"]["render_gl_device"]["items"] opengl_options = default_items + opengl_devices - widgets['render_gl_device'].clear() - widgets['render_gl_device'].addItems(opengl_options) - widgets['render_gl_device'].device_map = device_map + widgets["render_gl_device"].clear() + widgets["render_gl_device"].addItems(opengl_options) + widgets["render_gl_device"].device_map = device_map else: - widgets['render_gl_device'].setEnabled(False) - widgets['render_gl_device'].setToolTip("glxinfo not available - OpenGL device selection disabled") + widgets["render_gl_device"].setEnabled(False) + widgets["render_gl_device"].setToolTip("glxinfo not available - OpenGL device selection disabled") if GPULaunchManager.get_available_vulkaninfo(): vulkan_devices, device_map = GPULaunchManager.get_vulkan_device_options() - default_items = GPULaunchManager.GPU_SETTINGS_CATEGORIES["RenderSelector"]['render_vk_device']['items'] + default_items = GPULaunchManager.GPU_SETTINGS_CATEGORIES["RenderSelector"]["render_vk_device"]["items"] vulkan_options = default_items + vulkan_devices - widgets['render_vk_device'].clear() - widgets['render_vk_device'].addItems(vulkan_options) - widgets['render_vk_device'].device_map = device_map + widgets["render_vk_device"].clear() + widgets["render_vk_device"].addItems(vulkan_options) + widgets["render_vk_device"].device_map = device_map else: - widgets['render_vk_device'].setEnabled(False) - widgets['render_vk_device'].setToolTip("vulkaninfo not available - Vulkan device selection disabled") + widgets["render_vk_device"].setEnabled(False) + widgets["render_vk_device"].setToolTip("vulkaninfo not available - Vulkan device selection disabled") if category_name == "MangoHud": if not GPULaunchManager.get_available_mangohud(): for widget_key, widget in widgets.items(): - if hasattr(widget, 'setEnabled'): + if hasattr(widget, "setEnabled"): widget.setEnabled(False) widget.setToolTip("MangoHUD not available - MangoHUD options disabled") if category_name == "LSFrameGen": if not GPULaunchManager.get_available_lsfg(): for widget_key, widget in widgets.items(): - if hasattr(widget, 'setEnabled'): + if hasattr(widget, "setEnabled"): widget.setEnabled(False) widget.setToolTip("lsfg-vk not available - lsfg-vk options disabled") elif isinstance(widget, QLineEdit): @@ -1029,7 +1035,7 @@ def create_launch_options_tab(): scroll_area.setWidget(scroll_widget) main_layout.addWidget(scroll_area) - widgets = {'launch_options_input': launch_options_input} + widgets = {"launch_options_input": launch_options_input} GPULaunchManager.create_launch_apply_button(main_layout, widgets) @@ -1065,12 +1071,12 @@ def create_launch_apply_button(layout, widgets): button_layout = QHBoxLayout(button_container) button_layout.setContentsMargins(11, 10, 11, 0) - widgets['launch_apply_button'] = QPushButton("Apply") - widgets['launch_apply_button'].setMinimumSize(100, 30) - widgets['launch_apply_button'].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + widgets["launch_apply_button"] = QPushButton("Apply") + widgets["launch_apply_button"].setMinimumSize(100, 30) + widgets["launch_apply_button"].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button_layout.addStretch(1) - button_layout.addWidget(widgets['launch_apply_button']) + button_layout.addWidget(widgets["launch_apply_button"]) button_layout.addStretch(1) layout.addWidget(button_container) layout.addSpacing(9) @@ -1086,17 +1092,18 @@ def generate_env_vars(widgets, category_name): if category_name == "MangoHud": mangohud_parts = [] has_non_default_settings = False + has_default_settings = False for setting_key, widget in widgets.items(): - if setting_key.endswith('_apply_button') or setting_key.endswith('_browse') or setting_key.endswith('_clear'): + if setting_key.endswith("_apply_button") or setting_key.endswith("_browse") or setting_key.endswith("_clear"): continue - if setting_key == 'mangohud_enable': + if setting_key == "mangohud_enable": continue setting_info = GPULaunchManager.GPU_SETTINGS_CATEGORIES[category_name][setting_key] - if setting_info.get('path', False): + if setting_info.get("path", False): value = widget.text().strip() if not value: continue @@ -1105,41 +1112,42 @@ def generate_env_vars(widgets, category_name): if value == "unset": continue elif "(default)" in value: + has_default_settings = True continue else: has_non_default_settings = True - if 'env_mapping' not in setting_info: + if "env_mapping" not in setting_info: continue - mapping = setting_info['env_mapping'] + mapping = setting_info["env_mapping"] - if mapping.get('direct_value', False): - prefix = mapping.get('prefix', '') - if value == 'unlimited': - mapped_value = mapping['values'].get(value, '0') - mangohud_parts.append(f'{prefix}{mapped_value}') + if mapping.get("direct_value", False): + prefix = mapping.get("prefix", "") + if value == "unlimited": + mapped_value = mapping["values"].get(value, "0") + mangohud_parts.append(f"{prefix}{mapped_value}") else: - mangohud_parts.append(f'{prefix}{value}') - elif 'values' in mapping: - mapped_value = mapping['values'].get(value) + mangohud_parts.append(f"{prefix}{value}") + elif "values" in mapping: + mapped_value = mapping["values"].get(value) if mapped_value: - prefix = mapping.get('prefix', '') - mangohud_parts.append(f'{prefix}{mapped_value}') + prefix = mapping.get("prefix", "") + mangohud_parts.append(f"{prefix}{mapped_value}") if mangohud_parts and has_non_default_settings: - config_value = ','.join(mangohud_parts) - env_vars.append(f'MANGOHUD_CONFIG={config_value}') - elif not has_non_default_settings: - unset_vars.append('MANGOHUD_CONFIG') + config_value = ",".join(mangohud_parts) + env_vars.append(f"MANGOHUD_CONFIG={config_value}") + elif has_default_settings and not has_non_default_settings: + unset_vars.append("MANGOHUD_CONFIG") else: for setting_key, widget in widgets.items(): - if setting_key.endswith('_apply_button') or setting_key.endswith('_browse') or setting_key.endswith('_clear'): + if setting_key.endswith("_apply_button") or setting_key.endswith("_browse") or setting_key.endswith("_clear"): continue setting_info = GPULaunchManager.GPU_SETTINGS_CATEGORIES[category_name][setting_key] - if setting_info.get('path', False): + if setting_info.get("path", False): value = widget.text().strip() if not value: continue @@ -1147,33 +1155,33 @@ def generate_env_vars(widgets, category_name): value = widget.currentText() if value == "unset": continue - elif "(default)" in value and 'env_mapping' in setting_info: - mapping = setting_info['env_mapping'] - unset_vars.extend(mapping['var_names']) + elif "(default)" in value and "env_mapping" in setting_info: + mapping = setting_info["env_mapping"] + unset_vars.extend(mapping["var_names"]) continue - if 'env_mapping' not in setting_info: + if "env_mapping" not in setting_info: continue - mapping = setting_info['env_mapping'] - var_names = mapping['var_names'] + mapping = setting_info["env_mapping"] + var_names = mapping["var_names"] - if mapping.get('direct_value', False): + if mapping.get("direct_value", False): final_value = value - elif mapping.get('extract_prefix', False): - final_value = value.split(' - ')[0] - elif mapping.get('convert_to_bytes', False): + elif mapping.get("extract_prefix", False): + final_value = value.split(" - ")[0] + elif mapping.get("convert_to_bytes", False): final_value = str(int(value) * 1073741824) - elif 'values' in mapping: - mapped_value = mapping['values'].get(value) - prefix = mapping.get('prefix', '') + elif "values" in mapping: + mapped_value = mapping["values"].get(value) + prefix = mapping.get("prefix", "") final_value = f"{prefix}{mapped_value}" if mapped_value else None else: final_value = None if final_value is not None: for var_name in var_names: - env_vars.append(f'{var_name}={final_value}') + env_vars.append(f"{var_name}={final_value}") return env_vars, unset_vars @@ -1185,22 +1193,22 @@ def generate_render_selector_env_vars(render_widgets): env_vars = [] unset_vars = [] - if 'render_gl_device' in render_widgets: - selected = render_widgets['render_gl_device'].currentText() + if "render_gl_device" in render_widgets: + selected = render_widgets["render_gl_device"].currentText() if "(default)" in selected: unset_vars.extend(["__GLX_VENDOR_LIBRARY_NAME", "LIBGL_ALWAYS_SOFTWARE", "MESA_LOADER_DRIVER_OVERRIDE", "LIBGL_KOPPER_DRI2", "DRI_PRIME"]) elif selected != "unset": - device_map = getattr(render_widgets['render_gl_device'], 'device_map', {}) + device_map = getattr(render_widgets["render_gl_device"], "device_map", {}) env_dict = device_map.get(selected, {}) for var, value in env_dict.items(): env_vars.append(f"{var}={value}") - if 'render_vk_device' in render_widgets: - vulkan_selection = render_widgets['render_vk_device'].currentText() + if "render_vk_device" in render_widgets: + vulkan_selection = render_widgets["render_vk_device"].currentText() if "(default)" in vulkan_selection: unset_vars.extend(["MESA_VK_DEVICE_SELECT", "VK_DRIVER_FILES", "VK_ICD_FILENAMES"]) elif vulkan_selection != "unset": - device_map = getattr(render_widgets['render_vk_device'], 'device_map', {}) + device_map = getattr(render_widgets["render_vk_device"], "device_map", {}) device_key = device_map.get(vulkan_selection) if device_key: env_vars.append(f"MESA_VK_DEVICE_SELECT={device_key}!") @@ -1219,10 +1227,10 @@ def write_settings_file(mesa_widgets, nvidia_widgets, render_selector_widgets, m ls_frame_gen_env_vars, lsfg_unset = GPULaunchManager.generate_env_vars(ls_frame_gen_widgets, "LSFrameGen") launch_options = "" - if 'launch_options_input' in launch_options_widgets: - launch_options = launch_options_widgets['launch_options_input'].text().strip() + if "launch_options_input" in launch_options_widgets: + launch_options = launch_options_widgets["launch_options_input"].text().strip() - mangohud_enabled = ('mangohud_enable' in mangohud_widgets and mangohud_widgets['mangohud_enable'].currentText() == "on") + mangohud_enabled = ("mangohud_enable" in mangohud_widgets and mangohud_widgets["mangohud_enable"].currentText() == "on") if mangohud_enabled and launch_options: launch_options = f"mangohud {launch_options}" @@ -1232,7 +1240,7 @@ def write_settings_file(mesa_widgets, nvidia_widgets, render_selector_widgets, m all_env_vars = mesa_env_vars + nvidia_env_vars + render_env_vars + mangohud_env_vars + ls_frame_gen_env_vars all_unset_vars = mesa_unset + nvidia_unset + render_unset + mangohud_unset + lsfg_unset - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.conf') as temp_file: + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".conf") as temp_file: for env_var in all_env_vars: temp_file.write(f"{env_var}\n") diff --git a/src/kernel.py b/src/kernel.py index 36306f0..0360b02 100644 --- a/src/kernel.py +++ b/src/kernel.py @@ -6,315 +6,315 @@ class KernelManager: KERNEL_SETTINGS_CATEGORIES = { "CPU": { - 'sched_cfs_bandwidth_slice_us': { - 'path': '/proc/sys/kernel/sched_cfs_bandwidth_slice_us', - 'text': 'CFS bandwidth slice duration (microseconds). Higher values reduce scheduler overhead for CPU-bound applications.\nRecommended: 4000', - 'is_dynamic': False - }, - 'sched_autogroup_enabled': { - 'path': '/proc/sys/kernel/sched_autogroup_enabled', - 'text': 'Automatic process grouping for desktop responsiveness. Helps prioritize foreground applications.\nRecommended: 1 or 0 to use nice', - 'is_dynamic': False - }, - 'sched_rt_runtime_us': { - 'path': '/proc/sys/kernel/sched_rt_runtime_us', - 'text': 'Maximum CPU time (microseconds) for realtime tasks per period. -1 allows unlimited usage.\nRecommended: 950000 or -1', - 'is_dynamic': False - }, - 'sched_rt_period_us': { - 'path': '/proc/sys/kernel/sched_rt_period_us', - 'text': 'Period over which realtime task CPU usage is measured (microseconds). Works with sched_rt_runtime_us.\nRecommended: 1000000 (1 second, default)', - 'is_dynamic': False - }, - 'sched_schedstats': { - 'path': '/proc/sys/kernel/sched_schedstats', - 'text': 'Scheduler statistics collection. Disable to eliminate tracing overhead.\nRecommended: 0', - 'is_dynamic': False - }, - 'timer_migration': { - 'path': '/proc/sys/kernel/timer_migration', - 'text': 'Allows timer interrupts to migrate between CPUs. Disabling reduces latency at cost of power efficiency.\nRecommended: 0', - 'is_dynamic': False - }, - 'numa_balancing': { - 'path': '/proc/sys/kernel/numa_balancing', - 'text': 'Automatic NUMA memory migration. Creates overhead without significant benefits for most workloads.\nRecommended: 0', - 'is_dynamic': False + "sched_cfs_bandwidth_slice_us": { + "path": "/proc/sys/kernel/sched_cfs_bandwidth_slice_us", + "text": "CFS bandwidth slice duration (microseconds). Higher values reduce scheduler overhead for CPU-bound applications.\nRecommended: 4000", + "is_dynamic": False + }, + "sched_autogroup_enabled": { + "path": "/proc/sys/kernel/sched_autogroup_enabled", + "text": "Automatic process grouping for desktop responsiveness. Helps prioritize foreground applications.\nRecommended: 1 or 0 to use nice", + "is_dynamic": False + }, + "sched_rt_runtime_us": { + "path": "/proc/sys/kernel/sched_rt_runtime_us", + "text": "Maximum CPU time (microseconds) for realtime tasks per period. -1 allows unlimited usage.\nRecommended: 950000 or -1", + "is_dynamic": False + }, + "sched_rt_period_us": { + "path": "/proc/sys/kernel/sched_rt_period_us", + "text": "Period over which realtime task CPU usage is measured (microseconds). Works with sched_rt_runtime_us.\nRecommended: 1000000 (1 second, default)", + "is_dynamic": False + }, + "sched_schedstats": { + "path": "/proc/sys/kernel/sched_schedstats", + "text": "Scheduler statistics collection. Disable to eliminate tracing overhead.\nRecommended: 0", + "is_dynamic": False + }, + "timer_migration": { + "path": "/proc/sys/kernel/timer_migration", + "text": "Allows timer interrupts to migrate between CPUs. Disabling reduces latency at cost of power efficiency.\nRecommended: 0", + "is_dynamic": False + }, + "numa_balancing": { + "path": "/proc/sys/kernel/numa_balancing", + "text": "Automatic NUMA memory migration. Creates overhead without significant benefits for most workloads.\nRecommended: 0", + "is_dynamic": False } }, "Memory": { - 'compaction_proactiveness': { - 'path': '/proc/sys/vm/compaction_proactiveness', - 'text': 'Controls memory compaction aggressiveness (0-100). Lower values reduce CPU overhead during intensive workloads.\nRecommended: 0', - 'is_dynamic': False - }, - 'watermark_boost_factor': { - 'path': '/proc/sys/vm/watermark_boost_factor', - 'text': 'Memory reclaim aggressiveness during fragmentation (units of 10,000). Lower values prevent background reclaim during high-load scenarios.\nRecommended: 0', - 'is_dynamic': False - }, - 'watermark_scale_factor': { - 'path': '/proc/sys/vm/watermark_scale_factor', - 'text': 'Controls kswapd aggressiveness (units of 10,000). Higher values mean more free memory maintained. Default is 10 (0.1% of memory).\nRecommended: 10 (default) or higher for latency-sensitive workloads', - 'is_dynamic': False - }, - 'extfrag_threshold': { - 'path': '/proc/sys/vm/extfrag_threshold', - 'text': 'External fragmentation threshold that triggers compaction (0-1000). Higher values reduce compaction overhead.\nRecommended: 500', - 'is_dynamic': False - }, - 'compact_unevictable_allowed': { - 'path': '/proc/sys/vm/compact_unevictable_allowed', - 'text': 'Allow compaction to examine unevictable (mlocked) pages. May cause minor page faults but improves compaction effectiveness.\nRecommended: 1 (default), 0 for RT systems', - 'is_dynamic': False - }, - 'defrag_mode': { - 'path': '/proc/sys/vm/defrag_mode', - 'text': 'Proactive fragmentation prevention for hugepage allocations. Reduces long-term fragmentation at cost of immediate overhead.\nRecommended: 0(less overhead), 1 (for long-running systems)', - 'is_dynamic': False - }, - 'swappiness': { - 'path': '/proc/sys/vm/swappiness', - 'text': 'Kernel preference for swap vs RAM reclaim (0-200). Lower values prioritize keeping data in RAM for latency-sensitive workloads.\nRecommended: 10 (16GB+ RAM), 30-60 (8GB RAM). If using zram or zswap, higher values (60-120) are recommended to take advantage of compressed swap benefits.', - 'is_dynamic': False - }, - 'page-cluster': { - 'path': '/proc/sys/vm/page-cluster', - 'text': 'Number of pages to read/write together during swap operations as log2. Set to 0 for SSD-based systems.\nRecommended: 0', - 'is_dynamic': False - }, - 'vfs_cache_pressure': { - 'path': '/proc/sys/vm/vfs_cache_pressure', - 'text': 'Tenderness to reclaim filesystem caches relative to pagecache/swap. Lower values improve asset loading performance by keeping metadata cached.\nRecommended: 50', - 'is_dynamic': False - }, - 'min_free_kbytes': { - 'path': '/proc/sys/vm/min_free_kbytes', - 'text': 'Minimum reserved memory. Do not set below 1024 KB or above 5% of system memory.\nRecommended values: 1024-...', - 'is_dynamic': False - }, - 'overcommit_memory': { - 'path': '/proc/sys/vm/overcommit_memory', - 'text': 'Memory overcommit policy: 0=heuristic, 1=always, 2=strict. Mode 1 maximizes available memory.\nRecommended: 1', - 'is_dynamic': False - }, - 'overcommit_ratio': { - 'path': '/proc/sys/vm/overcommit_ratio', - 'text': 'Percentage of physical RAM (plus swap) available when overcommit_memory=2.\nRecommended: 60', - 'is_dynamic': False - }, - 'admin_reserve_kbytes': { - 'path': '/proc/sys/vm/admin_reserve_kbytes', - 'text': 'Memory reserved for root processes during OOM conditions. Default: min(3% of RAM, 8MB).\nRecommended: 8192 (8GB+ RAM), 4096 (4GB RAM)', - 'is_dynamic': False - }, - 'user_reserve_kbytes': { - 'path': '/proc/sys/vm/user_reserve_kbytes', - 'text': 'Memory reserved for user processes when overcommit_memory=2. Default: min(3% of process size, 128MB).\nRecommended: 131072', - 'is_dynamic': False - }, - 'max_map_count': { - 'path': '/proc/sys/vm/max_map_count', - 'text': 'Maximum memory mappings per process. Essential for applications using many shared libraries and memory-mapped files.\nRecommended: 2147483642 (SteamDeck Value) or 1048576 (Arch Linux)', - 'is_dynamic': False - }, - 'page_lock_unfairness': { - 'path': '/proc/sys/vm/page_lock_unfairness', - 'text': 'Number of times page lock can be stolen from waiter before fair handoff. Higher values favor readers which can improve read performance for asset streaming workloads.\nRecommended: 5', - 'is_dynamic': False - }, - 'percpu_pagelist_high_fraction': { - 'path': '/proc/sys/vm/percpu_pagelist_high_fraction', - 'text': 'Per-CPU page list size as fraction of zone size (0=default kernel algorithm, min value is 8). Higher values reduce contention on multi-core systems.\nRecommended: 8+ or 0 (reverts to the default behavior)', - 'is_dynamic': False - }, - 'zone_reclaim_mode': { - 'path': '/proc/sys/vm/zone_reclaim_mode', - 'text': 'NUMA memory reclaim behavior (bitmask: 0=reclaim off 1=reclaim on, 2=write dirty pages, 4=swap pages). Usually degrades performance due to unnecessary reclaim overhead.\nRecommended: 0', - 'is_dynamic': False - }, - 'min_unmapped_ratio': { - 'path': '/proc/sys/vm/min_unmapped_ratio', - 'text': 'Minimum percentage of unmapped pages before zone reclaim (NUMA only). Higher values delay local reclaim, improving cache locality.\nRecommended: 1', - 'is_dynamic': False - }, - 'min_slab_ratio': { - 'path': '/proc/sys/vm/min_slab_ratio', - 'text': 'Percentage of zone pages that must be reclaimable slab before zone reclaim (NUMA only). Higher values delay expensive remote allocation.\nRecommended: 5 (default), 3-8 range for tuning', - 'is_dynamic': False - }, - 'numa_stat': { - 'path': '/proc/sys/vm/numa_stat', - 'text': 'Enable NUMA statistics collection. Disabling reduces allocation overhead but breaks monitoring tools.\nRecommended: 0 (performance), 1 (monitoring/debugging)', - 'is_dynamic': False - }, - 'nr_hugepages': { - 'path': '/proc/sys/vm/nr_hugepages', - 'text': 'Number of persistent hugepages allocated. Critical for applications requiring guaranteed hugepage memory (databases, VMs, HPC).\nRecommended: 0 (default), or calculated based on application needs', - 'is_dynamic': False - }, - 'nr_overcommit_hugepages': { - 'path': '/proc/sys/vm/nr_overcommit_hugepages', - 'text': 'Maximum number of additional hugepages that can be allocated dynamically beyond nr_hugepages.\nRecommended: 0 (conservative), or set based on peak demand', - 'is_dynamic': False - }, - 'hugetlb_optimize_vmemmap': { - 'path': '/proc/sys/vm/hugetlb_optimize_vmemmap', - 'text': 'Optimize hugepage metadata memory usage (saves ~7 pages per 2MB hugepage). May add overhead during allocation/deallocation.\nRecommended: 1 (enable for memory savings), 0 (disable for allocation speed)', - 'is_dynamic': False - }, - 'stat_interval': { - 'path': '/proc/sys/vm/stat_interval', - 'text': 'VM statistics update interval (seconds). Higher values reduce CPU overhead.\nRecommended: 10', - 'is_dynamic': False - }, - 'thp_enabled': { - 'path': '/sys/kernel/mm/transparent_hugepage/enabled', - 'text': 'Transparent Huge Pages reduce TLB pressure but may cause allocation stalls. "madvise" enables only where beneficial.\nRecommended: madvise', - 'is_dynamic': True - }, - 'thp_shmem_enabled': { - 'path': '/sys/kernel/mm/transparent_hugepage/shmem_enabled', - 'text': 'THP for shared memory segments. "advise" enables only when explicitly requested.\nRecommended: advise', - 'is_dynamic': True - }, - 'thp_defrag': { - 'path': '/sys/kernel/mm/transparent_hugepage/defrag', - 'text': 'THP defragmentation strategy. "defer" prevents allocation stalls during high-priority tasks.\nRecommended: defer', - 'is_dynamic': True + "compaction_proactiveness": { + "path": "/proc/sys/vm/compaction_proactiveness", + "text": "Controls memory compaction aggressiveness (0-100). Lower values reduce CPU overhead during intensive workloads.\nRecommended: 0", + "is_dynamic": False + }, + "watermark_boost_factor": { + "path": "/proc/sys/vm/watermark_boost_factor", + "text": "Memory reclaim aggressiveness during fragmentation (units of 10,000). Lower values prevent background reclaim during high-load scenarios.\nRecommended: 0", + "is_dynamic": False + }, + "watermark_scale_factor": { + "path": "/proc/sys/vm/watermark_scale_factor", + "text": "Controls kswapd aggressiveness (units of 10,000). Higher values mean more free memory maintained. Default is 10 (0.1% of memory).\nRecommended: 10 (default) or higher for latency-sensitive workloads", + "is_dynamic": False + }, + "extfrag_threshold": { + "path": "/proc/sys/vm/extfrag_threshold", + "text": "External fragmentation threshold that triggers compaction (0-1000). Higher values reduce compaction overhead.\nRecommended: 500", + "is_dynamic": False + }, + "compact_unevictable_allowed": { + "path": "/proc/sys/vm/compact_unevictable_allowed", + "text": "Allow compaction to examine unevictable (mlocked) pages. May cause minor page faults but improves compaction effectiveness.\nRecommended: 1 (default), 0 for RT systems", + "is_dynamic": False + }, + "defrag_mode": { + "path": "/proc/sys/vm/defrag_mode", + "text": "Proactive fragmentation prevention for hugepage allocations. Reduces long-term fragmentation at cost of immediate overhead.\nRecommended: 0(less overhead), 1 (for long-running systems)", + "is_dynamic": False + }, + "swappiness": { + "path": "/proc/sys/vm/swappiness", + "text": "Kernel preference for swap vs RAM reclaim (0-200). Lower values prioritize keeping data in RAM for latency-sensitive workloads.\nRecommended: 10 (16GB+ RAM), 30-60 (8GB RAM). If using zram or zswap, higher values (60-120) are recommended to take advantage of compressed swap benefits.", + "is_dynamic": False + }, + "page-cluster": { + "path": "/proc/sys/vm/page-cluster", + "text": "Number of pages to read/write together during swap operations as log2. Set to 0 for SSD-based systems.\nRecommended: 0", + "is_dynamic": False + }, + "vfs_cache_pressure": { + "path": "/proc/sys/vm/vfs_cache_pressure", + "text": "Tenderness to reclaim filesystem caches relative to pagecache/swap. Lower values improve asset loading performance by keeping metadata cached.\nRecommended: 50", + "is_dynamic": False + }, + "min_free_kbytes": { + "path": "/proc/sys/vm/min_free_kbytes", + "text": "Minimum reserved memory. Do not set below 1024 KB or above 5% of system memory.\nRecommended values: 1024-...", + "is_dynamic": False + }, + "overcommit_memory": { + "path": "/proc/sys/vm/overcommit_memory", + "text": "Memory overcommit policy: 0=heuristic, 1=always, 2=strict. Mode 1 maximizes available memory.\nRecommended: 1", + "is_dynamic": False + }, + "overcommit_ratio": { + "path": "/proc/sys/vm/overcommit_ratio", + "text": "Percentage of physical RAM (plus swap) available when overcommit_memory=2.\nRecommended: 60", + "is_dynamic": False + }, + "admin_reserve_kbytes": { + "path": "/proc/sys/vm/admin_reserve_kbytes", + "text": "Memory reserved for root processes during OOM conditions. Default: min(3% of RAM, 8MB).\nRecommended: 8192 (8GB+ RAM), 4096 (4GB RAM)", + "is_dynamic": False + }, + "user_reserve_kbytes": { + "path": "/proc/sys/vm/user_reserve_kbytes", + "text": "Memory reserved for user processes when overcommit_memory=2. Default: min(3% of process size, 128MB).\nRecommended: 131072", + "is_dynamic": False + }, + "max_map_count": { + "path": "/proc/sys/vm/max_map_count", + "text": "Maximum memory mappings per process. Essential for applications using many shared libraries and memory-mapped files.\nRecommended: 2147483642 (SteamDeck Value) or 1048576 (Arch Linux)", + "is_dynamic": False + }, + "page_lock_unfairness": { + "path": "/proc/sys/vm/page_lock_unfairness", + "text": "Number of times page lock can be stolen from waiter before fair handoff. Higher values favor readers which can improve read performance for asset streaming workloads.\nRecommended: 5", + "is_dynamic": False + }, + "percpu_pagelist_high_fraction": { + "path": "/proc/sys/vm/percpu_pagelist_high_fraction", + "text": "Per-CPU page list size as fraction of zone size (0=default kernel algorithm, min value is 8). Higher values reduce contention on multi-core systems.\nRecommended: 8+ or 0 (reverts to the default behavior)", + "is_dynamic": False + }, + "zone_reclaim_mode": { + "path": "/proc/sys/vm/zone_reclaim_mode", + "text": "NUMA memory reclaim behavior (bitmask: 0=reclaim off 1=reclaim on, 2=write dirty pages, 4=swap pages). Usually degrades performance due to unnecessary reclaim overhead.\nRecommended: 0", + "is_dynamic": False + }, + "min_unmapped_ratio": { + "path": "/proc/sys/vm/min_unmapped_ratio", + "text": "Minimum percentage of unmapped pages before zone reclaim (NUMA only). Higher values delay local reclaim, improving cache locality.\nRecommended: 1", + "is_dynamic": False + }, + "min_slab_ratio": { + "path": "/proc/sys/vm/min_slab_ratio", + "text": "Percentage of zone pages that must be reclaimable slab before zone reclaim (NUMA only). Higher values delay expensive remote allocation.\nRecommended: 5 (default), 3-8 range for tuning", + "is_dynamic": False + }, + "numa_stat": { + "path": "/proc/sys/vm/numa_stat", + "text": "Enable NUMA statistics collection. Disabling reduces allocation overhead but breaks monitoring tools.\nRecommended: 0 (performance), 1 (monitoring/debugging)", + "is_dynamic": False + }, + "nr_hugepages": { + "path": "/proc/sys/vm/nr_hugepages", + "text": "Number of persistent hugepages allocated. Critical for applications requiring guaranteed hugepage memory (databases, VMs, HPC).\nRecommended: 0 (default), or calculated based on application needs", + "is_dynamic": False + }, + "nr_overcommit_hugepages": { + "path": "/proc/sys/vm/nr_overcommit_hugepages", + "text": "Maximum number of additional hugepages that can be allocated dynamically beyond nr_hugepages.\nRecommended: 0 (conservative), or set based on peak demand", + "is_dynamic": False + }, + "hugetlb_optimize_vmemmap": { + "path": "/proc/sys/vm/hugetlb_optimize_vmemmap", + "text": "Optimize hugepage metadata memory usage (saves ~7 pages per 2MB hugepage). May add overhead during allocation/deallocation.\nRecommended: 1 (enable for memory savings), 0 (disable for allocation speed)", + "is_dynamic": False + }, + "stat_interval": { + "path": "/proc/sys/vm/stat_interval", + "text": "VM statistics update interval (seconds). Higher values reduce CPU overhead.\nRecommended: 10", + "is_dynamic": False + }, + "thp_enabled": { + "path": "/sys/kernel/mm/transparent_hugepage/enabled", + "text": "Transparent Huge Pages reduce TLB pressure but may cause allocation stalls. 'madvise' enables only where beneficial.\nRecommended: madvise", + "is_dynamic": True + }, + "thp_shmem_enabled": { + "path": "/sys/kernel/mm/transparent_hugepage/shmem_enabled", + "text": "THP for shared memory segments. 'advise' enables only when explicitly requested.\nRecommended: advise", + "is_dynamic": True + }, + "thp_defrag": { + "path": "/sys/kernel/mm/transparent_hugepage/defrag", + "text": "THP defragmentation strategy. 'defer' prevents allocation stalls during high-priority tasks.\nRecommended: defer", + "is_dynamic": True } }, "Disk": { - 'dirty_ratio': { - 'path': '/proc/sys/vm/dirty_ratio', - 'text': 'Maximum percentage of available memory for dirty pages before synchronous writes. Lower values reduce I/O latency spikes. Mutually exclusive with dirty_bytes.\nRecommended: 10', - 'is_dynamic': False - }, - 'dirty_background_ratio': { - 'path': '/proc/sys/vm/dirty_background_ratio', - 'text': 'Percentage of available memory at which background writeback begins. Should be 1/3 of dirty_ratio. Mutually exclusive with dirty_background_bytes.\nRecommended: 3', - 'is_dynamic': False - }, - 'dirty_bytes': { - 'path': '/proc/sys/vm/dirty_bytes', - 'text': 'Absolute dirty memory limit (bytes). Provides consistent behavior regardless of RAM size. Mutually exclusive with dirty_ratio.\nRecommended: 67108864 (64MB)', - 'is_dynamic': False - }, - 'dirty_background_bytes': { - 'path': '/proc/sys/vm/dirty_background_bytes', - 'text': 'Absolute background writeback threshold (bytes). Should be 50% of dirty_bytes. Mutually exclusive with dirty_background_ratio.\nRecommended: 33554432 (32MB)', - 'is_dynamic': False - }, - 'dirty_expire_centisecs': { - 'path': '/proc/sys/vm/dirty_expire_centisecs', - 'text': 'Maximum time dirty data remains in memory (centiseconds). Shorter intervals improve responsiveness.\nRecommended: 3000', - 'is_dynamic': False - }, - 'dirty_writeback_centisecs': { - 'path': '/proc/sys/vm/dirty_writeback_centisecs', - 'text': 'Interval between periodic writeback wakeups (centiseconds). Longer intervals reduce CPU overhead.\nRecommended: 1500', - 'is_dynamic': False - }, - 'dirtytime_expire_seconds': { - 'path': '/proc/sys/vm/dirtytime_expire_seconds', - 'text': 'Interval for lazy timestamp updates on filesystems with dirtytime mount option (seconds).\nRecommended: 43200 (12 hours)', - 'is_dynamic': False - }, - 'laptop_mode': { - 'path': '/proc/sys/vm/laptop_mode', - 'text': 'Power-saving write delay mechanism. Disable for performance-oriented systems.\nRecommended: 0', - 'is_dynamic': False + "dirty_ratio": { + "path": "/proc/sys/vm/dirty_ratio", + "text": "Maximum percentage of available memory for dirty pages before synchronous writes. Lower values reduce I/O latency spikes. Mutually exclusive with dirty_bytes.\nRecommended: 10", + "is_dynamic": False + }, + "dirty_background_ratio": { + "path": "/proc/sys/vm/dirty_background_ratio", + "text": "Percentage of available memory at which background writeback begins. Should be 1/3 of dirty_ratio. Mutually exclusive with dirty_background_bytes.\nRecommended: 3", + "is_dynamic": False + }, + "dirty_bytes": { + "path": "/proc/sys/vm/dirty_bytes", + "text": "Absolute dirty memory limit (bytes). Provides consistent behavior regardless of RAM size. Mutually exclusive with dirty_ratio.\nRecommended: 67108864 (64MB)", + "is_dynamic": False + }, + "dirty_background_bytes": { + "path": "/proc/sys/vm/dirty_background_bytes", + "text": "Absolute background writeback threshold (bytes). Should be 50% of dirty_bytes. Mutually exclusive with dirty_background_ratio.\nRecommended: 33554432 (32MB)", + "is_dynamic": False + }, + "dirty_expire_centisecs": { + "path": "/proc/sys/vm/dirty_expire_centisecs", + "text": "Maximum time dirty data remains in memory (centiseconds). Shorter intervals improve responsiveness.\nRecommended: 3000", + "is_dynamic": False + }, + "dirty_writeback_centisecs": { + "path": "/proc/sys/vm/dirty_writeback_centisecs", + "text": "Interval between periodic writeback wakeups (centiseconds). Longer intervals reduce CPU overhead.\nRecommended: 1500", + "is_dynamic": False + }, + "dirtytime_expire_seconds": { + "path": "/proc/sys/vm/dirtytime_expire_seconds", + "text": "Interval for lazy timestamp updates on filesystems with dirtytime mount option (seconds).\nRecommended: 43200 (12 hours)", + "is_dynamic": False + }, + "laptop_mode": { + "path": "/proc/sys/vm/laptop_mode", + "text": "Power-saving write delay mechanism. Disable for performance-oriented systems.\nRecommended: 0", + "is_dynamic": False } }, "System": { - 'watchdog': { - 'path': '/proc/sys/kernel/watchdog', - 'text': 'Soft lockup detector. Disable to remove periodic checks that can cause stuttering.\nRecommended: 0', - 'is_dynamic': False - }, - 'nmi_watchdog': { - 'path': '/proc/sys/kernel/nmi_watchdog', - 'text': 'NMI-based hard lockup detection. Disables performance-counter based monitoring.\nRecommended: 0', - 'is_dynamic': False - }, - 'hung_task_timeout_secs': { - 'path': '/proc/sys/kernel/hung_task_timeout_secs', - 'text': 'Timeout for detecting hung tasks (seconds). 0 disables detection to prevent false positives during long sessions.\nRecommended: 0 or 120 (Default)', - 'is_dynamic': False - }, - 'pid_max': { - 'path': '/proc/sys/kernel/pid_max', - 'text': 'Maximum process ID value. Higher values support systems running many concurrent processes.\nRecommended: 4194304', - 'is_dynamic': False - }, - 'file_max': { - 'path': '/proc/sys/fs/file-max', - 'text': 'System-wide maximum open file descriptors. Essential for applications opening many files simultaneously.\nRecommended: 2097152', - 'is_dynamic': False - }, - 'oom_kill_allocating_task': { - 'path': '/proc/sys/vm/oom_kill_allocating_task', - 'text': 'OOM killer targets the task that triggered OOM instead of scanning all tasks. Improves recovery speed.\nRecommended: 1', - 'is_dynamic': False - }, - 'oom_dump_tasks': { - 'path': '/proc/sys/vm/oom_dump_tasks', - 'text': 'Enable task dump when OOM killer is invoked. Useful for debugging but adds overhead on large systems.\nRecommended: 0 (disable for performance), 1 (enable for debugging)', - 'is_dynamic': False - }, - 'panic_on_oom': { - 'path': '/proc/sys/vm/panic_on_oom', - 'text': 'System behavior on OOM: 0=kill process, 1=panic on system OOM, 2=always panic.\nRecommended: 0 (default behavior)', - 'is_dynamic': False - }, - 'max_user_freq': { - 'path': '/sys/class/rtc/rtc0/max_user_freq', - 'text': 'Maximum RTC interrupt frequency (Hz) for userspace. Higher values provide better timer precision.\nRecommended: 64', - 'is_dynamic': False + "watchdog": { + "path": "/proc/sys/kernel/watchdog", + "text": "Soft lockup detector. Disable to remove periodic checks that can cause stuttering.\nRecommended: 0", + "is_dynamic": False + }, + "nmi_watchdog": { + "path": "/proc/sys/kernel/nmi_watchdog", + "text": "NMI-based hard lockup detection. Disables performance-counter based monitoring.\nRecommended: 0", + "is_dynamic": False + }, + "hung_task_timeout_secs": { + "path": "/proc/sys/kernel/hung_task_timeout_secs", + "text": "Timeout for detecting hung tasks (seconds). 0 disables detection to prevent false positives during long sessions.\nRecommended: 0 or 120 (Default)", + "is_dynamic": False + }, + "pid_max": { + "path": "/proc/sys/kernel/pid_max", + "text": "Maximum process ID value. Higher values support systems running many concurrent processes.\nRecommended: 4194304", + "is_dynamic": False + }, + "file_max": { + "path": "/proc/sys/fs/file-max", + "text": "System-wide maximum open file descriptors. Essential for applications opening many files simultaneously.\nRecommended: 2097152", + "is_dynamic": False + }, + "oom_kill_allocating_task": { + "path": "/proc/sys/vm/oom_kill_allocating_task", + "text": "OOM killer targets the task that triggered OOM instead of scanning all tasks. Improves recovery speed.\nRecommended: 1", + "is_dynamic": False + }, + "oom_dump_tasks": { + "path": "/proc/sys/vm/oom_dump_tasks", + "text": "Enable task dump when OOM killer is invoked. Useful for debugging but adds overhead on large systems.\nRecommended: 0 (disable for performance), 1 (enable for debugging)", + "is_dynamic": False + }, + "panic_on_oom": { + "path": "/proc/sys/vm/panic_on_oom", + "text": "System behavior on OOM: 0=kill process, 1=panic on system OOM, 2=always panic.\nRecommended: 0 (default behavior)", + "is_dynamic": False + }, + "max_user_freq": { + "path": "/sys/class/rtc/rtc0/max_user_freq", + "text": "Maximum RTC interrupt frequency (Hz) for userspace. Higher values provide better timer precision.\nRecommended: 64", + "is_dynamic": False } }, "Network": { - 'core_rmem_max': { - 'path': '/proc/sys/net/core/rmem_max', - 'text': 'Maximum socket receive buffer size (bytes) per socket. Higher values improve throughput for high-bandwidth connections.\nRecommended: 268435456 (256MB)', - 'is_dynamic': False - }, - 'core_wmem_max': { - 'path': '/proc/sys/net/core/wmem_max', - 'text': 'Maximum socket send buffer size (bytes) per socket. Should match rmem_max for balanced performance.\nRecommended: 268435456 (256MB)', - 'is_dynamic': False - }, - 'tcp_fastopen': { - 'path': '/proc/sys/net/ipv4/tcp_fastopen', - 'text': 'TCP Fast Open reduces connection establishment latency. Bitmask: 1=client, 2=server.\nRecommended: 3 (enable for both incoming/outgoing)', - 'is_dynamic': False - }, - 'tcp_window_scaling': { - 'path': '/proc/sys/net/ipv4/tcp_window_scaling', - 'text': 'Enable TCP window scaling for high-bandwidth connections.\nRecommended: 1', - 'is_dynamic': False - }, - 'tcp_timestamps': { - 'path': '/proc/sys/net/ipv4/tcp_timestamps', - 'text': 'Enable TCP timestamps for RTT measurement and PAWS protection.\nRecommended: 1', - 'is_dynamic': False + "core_rmem_max": { + "path": "/proc/sys/net/core/rmem_max", + "text": "Maximum socket receive buffer size (bytes) per socket. Higher values improve throughput for high-bandwidth connections.\nRecommended: 268435456 (256MB)", + "is_dynamic": False + }, + "core_wmem_max": { + "path": "/proc/sys/net/core/wmem_max", + "text": "Maximum socket send buffer size (bytes) per socket. Should match rmem_max for balanced performance.\nRecommended: 268435456 (256MB)", + "is_dynamic": False + }, + "tcp_fastopen": { + "path": "/proc/sys/net/ipv4/tcp_fastopen", + "text": "TCP Fast Open reduces connection establishment latency. Bitmask: 1=client, 2=server.\nRecommended: 3 (enable for both incoming/outgoing)", + "is_dynamic": False + }, + "tcp_window_scaling": { + "path": "/proc/sys/net/ipv4/tcp_window_scaling", + "text": "Enable TCP window scaling for high-bandwidth connections.\nRecommended: 1", + "is_dynamic": False + }, + "tcp_timestamps": { + "path": "/proc/sys/net/ipv4/tcp_timestamps", + "text": "Enable TCP timestamps for RTT measurement and PAWS protection.\nRecommended: 1", + "is_dynamic": False } }, "Security": { - 'randomize_va_space': { - 'path': '/proc/sys/kernel/randomize_va_space', - 'text': 'Address space layout randomization: 0=disabled, 1=conservative, 2=full. Lower values reduce address translation overhead.\nRecommended: 0 (performance), 2 (security)', - 'is_dynamic': False - }, - 'perf_event_paranoid': { - 'path': '/proc/sys/kernel/perf_event_paranoid', - 'text': 'Performance monitoring access: -1=unrestricted, 0=user+kernel, 1=user only, 2=kernel only, 3=no access.\nRecommended: 2', - 'is_dynamic': False - }, - 'mmap_min_addr': { - 'path': '/proc/sys/vm/mmap_min_addr', - 'text': 'Minimum virtual address for mmap operations. Security feature with minimal performance impact.\nRecommended: 65536', - 'is_dynamic': False + "randomize_va_space": { + "path": "/proc/sys/kernel/randomize_va_space", + "text": "Address space layout randomization: 0=disabled, 1=conservative, 2=full. Lower values reduce address translation overhead.\nRecommended: 0 (performance), 2 (security)", + "is_dynamic": False + }, + "perf_event_paranoid": { + "path": "/proc/sys/kernel/perf_event_paranoid", + "text": "Performance monitoring access: -1=unrestricted, 0=user+kernel, 1=user only, 2=kernel only, 3=no access.\nRecommended: 2", + "is_dynamic": False + }, + "mmap_min_addr": { + "path": "/proc/sys/vm/mmap_min_addr", + "text": "Minimum virtual address for mmap operations. Security feature with minimal performance impact.\nRecommended: 65536", + "is_dynamic": False } } } @@ -329,7 +329,7 @@ def get_current_value(setting_path): Read and return current value from setting file """ try: - with open(setting_path, 'r') as f: + with open(setting_path, "r") as f: return f.read().strip() except Exception: return None @@ -340,10 +340,10 @@ def get_dynamic_current_value(setting_path): Extract current value from dynamic settings (e.g., [current] option1 option2) """ try: - with open(setting_path, 'r') as f: + with open(setting_path, "r") as f: content = f.read().strip() - match = re.search(r'\[([^\]]+)\]', content) + match = re.search(r"\[([^\]]+)\]", content) if match: return match.group(1) else: @@ -361,10 +361,10 @@ def get_dynamic_possible_values(setting_path): Extract all possible values from dynamic settings """ try: - with open(setting_path, 'r') as f: + with open(setting_path, "r") as f: content = f.read().strip() - clean_content = re.sub(r'[\[\]]', '', content) + clean_content = re.sub(r"[\[\]]", "", content) possible_values = clean_content.split() if possible_values: @@ -380,7 +380,7 @@ def get_available_setting(setting_path): Check if setting file is accessible """ try: - with open(setting_path, 'r') as f: + with open(setting_path, "r") as f: f.read() return True except Exception: @@ -424,9 +424,9 @@ def create_kernel_tab(main_window): kernel_layout.addWidget(kernel_subtabs) KernelManager.create_kernel_apply_button(kernel_layout, widgets, main_window) - widgets['kernel_settings_applied'] = False - widgets['is_process_running'] = False - widgets['process'] = None + widgets["kernel_settings_applied"] = False + widgets["is_process_running"] = False + widgets["process"] = None return kernel_tab, widgets @@ -447,14 +447,14 @@ def create_setting_section(kernel_layout, widgets, setting_name, setting_info): current_value_label = QLabel("Updating...") setting_layout.addWidget(current_value_label) - is_accessible = KernelManager.get_available_setting(setting_info['path']) + is_accessible = KernelManager.get_available_setting(setting_info["path"]) input_widget = QLineEdit() input_widget.setPlaceholderText("enter value") if is_accessible: - tooltip_text = setting_info['text'] - if setting_info['is_dynamic']: - possible_values = KernelManager.get_dynamic_possible_values(setting_info['path']) + tooltip_text = setting_info["text"] + if setting_info["is_dynamic"]: + possible_values = KernelManager.get_dynamic_possible_values(setting_info["path"]) if possible_values: values_text = " ".join(possible_values) tooltip_text += f"\nPossible values: {values_text}" @@ -468,8 +468,8 @@ def create_setting_section(kernel_layout, widgets, setting_name, setting_info): setting_layout.addWidget(input_widget) - widgets[f'{setting_name}_input'] = input_widget - widgets[f'{setting_name}_current_value'] = current_value_label + widgets[f"{setting_name}_input"] = input_widget + widgets[f"{setting_name}_current_value"] = current_value_label kernel_layout.addWidget(setting_container) @staticmethod @@ -482,12 +482,12 @@ def create_kernel_apply_button(kernel_layout, widgets, main_window): button_layout = QHBoxLayout(button_container) button_layout.setContentsMargins(10, 10, 10, 0) - widgets['kernel_apply_button'] = QPushButton("Apply") - widgets['kernel_apply_button'].setMinimumSize(100, 30) - widgets['kernel_apply_button'].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + widgets["kernel_apply_button"] = QPushButton("Apply") + widgets["kernel_apply_button"].setMinimumSize(100, 30) + widgets["kernel_apply_button"].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button_layout.addStretch(1) - button_layout.addWidget(widgets['kernel_apply_button']) + button_layout.addWidget(widgets["kernel_apply_button"]) button_layout.addStretch(1) kernel_layout.addWidget(button_container) @@ -500,22 +500,22 @@ def refresh_kernel_values(widgets): """ for category in KernelManager.KERNEL_SETTINGS_CATEGORIES.values(): for name, info in category.items(): - if not KernelManager.get_available_setting(info['path']): - widgets[f'{name}_current_value'].setText("current value: not available") + if not KernelManager.get_available_setting(info["path"]): + widgets[f"{name}_current_value"].setText("current value: not available") continue - if info['is_dynamic']: - current = KernelManager.get_dynamic_current_value(info['path']) - tooltip_text = info['text'] - possible_values = KernelManager.get_dynamic_possible_values(info['path']) + if info["is_dynamic"]: + current = KernelManager.get_dynamic_current_value(info["path"]) + tooltip_text = info["text"] + possible_values = KernelManager.get_dynamic_possible_values(info["path"]) if possible_values: values_text = " ".join(possible_values) tooltip_text += f"\nPossible values: {values_text}" else: tooltip_text += "\nPossible values: Unable to read from system" - widgets[f'{name}_input'].setToolTip(tooltip_text) + widgets[f"{name}_input"].setToolTip(tooltip_text) else: - current = KernelManager.get_current_value(info['path']) + current = KernelManager.get_current_value(info["path"]) if current is not None: - widgets[f'{name}_current_value'].setText(f"current value: {current}") + widgets[f"{name}_current_value"].setText(f"current value: {current}") diff --git a/src/options.py b/src/options.py index 521446d..16c676b 100644 --- a/src/options.py +++ b/src/options.py @@ -8,57 +8,63 @@ class OptionsManager: OPTIONS_SETTINGS_CATEGORIES = { "Appearance": { - 'theme': { - 'label': 'Selected Theme:', - 'text': 'Visual theme for the application interface.', - 'items': ["amd", "intel", "nvidia"], - 'default': 'amd' + "theme": { + "label": "Selected Theme:", + "text": "Visual theme for the application interface.", + "items": ["amd", "intel", "nvidia"], + "default": "amd" }, - 'transparency': { - 'label': 'Transparency:', - 'text': 'Window transparency.', - 'items': ["enable", "disable"], - 'default': 'disable' + "transparency": { + "label": "Transparency:", + "text": "Window transparency.", + "items": ["enable", "disable"], + "default": "disable" } }, "Window Behavior": { - 'systray': { - 'label': 'Run in System Tray:', - 'text': 'Run the application in system tray. When enabled, closing the window minimizes to tray instead of exiting.', - 'items': ["enable", "disable"], - 'default': 'disable' + "systray": { + "label": "Run in System Tray:", + "text": "Run the application in system tray. When enabled, closing the window minimizes to tray instead of exiting.", + "items": ["enable", "disable"], + "default": "disable" }, - 'start_maximized': { - 'label': 'Open Maximized:', - 'text': 'Start the application in maximized window mode when launched.', - 'items': ["enable", "disable"], - 'default': 'disable' + "start_maximized": { + "label": "Open Maximized:", + "text": "Start the application in maximized window mode when launched.", + "items": ["enable", "disable"], + "default": "disable" }, - 'start_minimized': { - 'label': 'Open Minimized:', - 'text': 'Start the application minimized to taskbar or system tray when launched.', - 'items': ["enable", "disable"], - 'default': 'disable' + "start_minimized": { + "label": "Open Minimized:", + "text": "Start the application minimized to taskbar or system tray when launched.", + "items": ["enable", "disable"], + "default": "disable" }, - 'scaling': { - 'label': 'Interface Scaling:', - 'text': 'Scale the interface size for high-DPI displays or better readability.', - 'items': ["1.0", "1.25", "1.5", "1.75", "2.0"], - 'default': '1.0' + "scaling": { + "label": "Interface Scaling:", + "text": "Scale the interface size for high-DPI displays or better readability.", + "items": ["1.0", "1.25", "1.5", "1.75", "2.0"], + "default": "1.0" } }, "Application": { - 'welcome_message': { - 'label': 'Welcome Message:', - 'text': 'Show the welcome message dialog when starting the application.', - 'items': ["enable", "disable"], - 'default': 'enable' + "welcome_message": { + "label": "Welcome Message:", + "text": "Show the welcome message dialog when starting the application.", + "items": ["enable", "disable"], + "default": "enable" }, - 'check_updates': { - 'label': 'Check for Updates:', - 'text': 'Check for new versions on startup (checks once per session).', - 'items': ["enable", "disable"], - 'default': 'disable' + "check_updates": { + "label": "Check for Updates:", + "text": "Check for new versions on startup (checks once per session).", + "items": ["enable", "disable"], + "default": "disable" + }, + "volt_path": { + "label": "volt Script Path:", + "text": "Location for the volt script. /usr/local/bin/volt allows global 'volt' command, while /tmp/volt requires the full path, but should work on inmutable/atomic distros.", + "items": ["/usr/local/bin/volt", "/tmp/volt"], + "default": "/usr/local/bin/volt" } } } @@ -90,17 +96,17 @@ def create_options_tab(main_window): for option_key, option_info in OptionsManager.OPTIONS_SETTINGS.items(): option_layout = QHBoxLayout() - option_label = QLabel(option_info['label']) + option_label = QLabel(option_info["label"]) option_label.setWordWrap(True) option_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) widgets[option_key] = QComboBox() - widgets[option_key].addItems(option_info['items']) - widgets[option_key].setCurrentText(option_info['default']) + widgets[option_key].addItems(option_info["items"]) + widgets[option_key].setCurrentText(option_info["default"]) widgets[option_key].setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) - if 'text' in option_info: - widgets[option_key].setToolTip(option_info['text']) + if "text" in option_info: + widgets[option_key].setToolTip(option_info["text"]) option_layout.addWidget(option_label) option_layout.addWidget(widgets[option_key]) @@ -112,9 +118,9 @@ def create_options_tab(main_window): OptionsManager.create_option_apply_button(main_layout, widgets, main_window) - widgets['main_window'] = main_window - widgets['options_path'] = Path(os.path.expanduser("~/.config/volt-gui/volt-options.ini")) - widgets['options_path'].parent.mkdir(parents=True, exist_ok=True) + widgets["main_window"] = main_window + widgets["options_path"] = Path(os.path.expanduser("~/.config/volt-gui/volt-options.ini")) + widgets["options_path"].parent.mkdir(parents=True, exist_ok=True) OptionsManager.set_default_values(widgets) @@ -130,12 +136,12 @@ def create_option_apply_button(parent_layout, widgets, main_window): button_layout = QHBoxLayout(button_container) button_layout.setContentsMargins(11, 10, 11, 0) - widgets['options_apply_button'] = QPushButton("Apply") - widgets['options_apply_button'].setMinimumSize(100, 30) - widgets['options_apply_button'].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + widgets["options_apply_button"] = QPushButton("Apply") + widgets["options_apply_button"].setMinimumSize(100, 30) + widgets["options_apply_button"].setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) button_layout.addStretch(1) - button_layout.addWidget(widgets['options_apply_button']) + button_layout.addWidget(widgets["options_apply_button"]) button_layout.addStretch(1) parent_layout.addWidget(button_container) @@ -147,7 +153,7 @@ def set_default_values(widgets): Set default values for all widgets. """ for option_key, option_info in OptionsManager.OPTIONS_SETTINGS.items(): - widgets[option_key].setCurrentText(option_info['default']) + widgets[option_key].setCurrentText(option_info["default"]) @staticmethod def get_early_scaling_factor(): @@ -156,18 +162,18 @@ def get_early_scaling_factor(): This method is called early in the main function. """ config_path = Path(os.path.expanduser("~/.config/volt-gui/volt-options.ini")) - scaling_factor = OptionsManager.OPTIONS_SETTINGS['scaling']['default'] + scaling_factor = OptionsManager.OPTIONS_SETTINGS["scaling"]["default"] if config_path.exists(): options = configparser.ConfigParser() options.read(config_path) scaling_factor = options.get( - 'Options', - 'scaling', - fallback=OptionsManager.OPTIONS_SETTINGS['scaling']['default'] + "Options", + "scaling", + fallback=OptionsManager.OPTIONS_SETTINGS["scaling"]["default"] ) - os.environ['QT_SCALE_FACTOR'] = scaling_factor + os.environ["QT_SCALE_FACTOR"] = scaling_factor return scaling_factor @staticmethod @@ -177,12 +183,12 @@ def load_options(widgets): """ OptionsManager.set_default_values(widgets) - if not widgets['options_path'].exists(): + if not widgets["options_path"].exists(): OptionsManager.save_options(widgets) return options = configparser.ConfigParser() - options.read(widgets['options_path']) + options.read(widgets["options_path"]) OptionsManager.apply_options_values(options, widgets) OptionsManager.apply_all_options(widgets) @@ -193,17 +199,17 @@ def save_options(widgets): Save current options to the configuration file. """ options = configparser.ConfigParser() - main_window = widgets['main_window'] + main_window = widgets["main_window"] - options['Options'] = {} + options["Options"] = {} for option_key in OptionsManager.OPTIONS_SETTINGS.keys(): - options['Options'][option_key] = widgets[option_key].currentText() + options["Options"][option_key] = widgets[option_key].currentText() - options['Profile'] = {'last_active_profile': getattr(main_window, 'current_profile', 'Default')} + options["Profile"] = {"last_active_profile": getattr(main_window, "current_profile", "Default")} - os.makedirs(os.path.dirname(widgets['options_path']), exist_ok=True) + os.makedirs(os.path.dirname(widgets["options_path"]), exist_ok=True) - with open(widgets['options_path'], 'w') as optionsfile: + with open(widgets["options_path"], "w") as optionsfile: options.write(optionsfile) OptionsManager.apply_all_options(widgets) @@ -213,13 +219,13 @@ def apply_options_values(options, widgets): """ Apply values from options file to widgets. """ - main_window = widgets['main_window'] + main_window = widgets["main_window"] for option_key, option_info in OptionsManager.OPTIONS_SETTINGS.items(): - value = options.get('Options', option_key, fallback=option_info['default']) + value = options.get("Options", option_key, fallback=option_info["default"]) widgets[option_key].setCurrentText(value) - last_profile = options.get('Profile', 'last_active_profile', fallback='Default') + last_profile = options.get("Profile", "last_active_profile", fallback="Default") index = main_window.profile_selector.findText(last_profile) if index >= 0: main_window.profile_selector.setCurrentText(last_profile) @@ -238,14 +244,15 @@ def apply_all_options(widgets): OptionsManager.apply_scaling_options(widgets) OptionsManager.apply_welcome_message_options(widgets) OptionsManager.apply_check_updates_options(widgets) + OptionsManager.apply_volt_path_options(widgets) @staticmethod def apply_theme_options(widgets): """ Apply the selected theme to the application. """ - main_window = widgets['main_window'] - theme_name = widgets['theme'].currentText() + main_window = widgets["main_window"] + theme_name = widgets["theme"].currentText() ThemeManager.apply_theme(QApplication.instance(), theme_name) @staticmethod @@ -253,20 +260,20 @@ def apply_system_tray_options(widgets): """ Apply system tray options to the main window. """ - main_window = widgets['main_window'] - run_in_tray = widgets['systray'].currentText() == OptionsManager.OPTIONS_SETTINGS['systray']['items'][0] + main_window = widgets["main_window"] + run_in_tray = widgets["systray"].currentText() == OptionsManager.OPTIONS_SETTINGS["systray"]["items"][0] old_option = main_window.use_system_tray main_window.use_system_tray = run_in_tray if old_option != run_in_tray: if run_in_tray: - if not hasattr(main_window, 'tray_icon'): + if not hasattr(main_window, "tray_icon"): main_window.setup_system_tray() else: - if hasattr(main_window, 'tray_icon'): + if hasattr(main_window, "tray_icon"): main_window.tray_icon.hide() main_window.tray_icon.deleteLater() - delattr(main_window, 'tray_icon') + delattr(main_window, "tray_icon") if not main_window.isVisible(): main_window.show_and_activate() @@ -277,8 +284,8 @@ def apply_transparency_options(widgets): """ Apply window transparency options to the main window. """ - main_window = widgets['main_window'] - transparency_enabled = widgets['transparency'].currentText() == OptionsManager.OPTIONS_SETTINGS['transparency']['items'][0] + main_window = widgets["main_window"] + transparency_enabled = widgets["transparency"].currentText() == OptionsManager.OPTIONS_SETTINGS["transparency"]["items"][0] if transparency_enabled: main_window.setWindowOpacity(0.9) else: @@ -289,8 +296,8 @@ def apply_start_minimized_options(widgets): """ Apply the start minimized option to the application. """ - main_window = widgets['main_window'] - start_minimized = widgets['start_minimized'].currentText() == OptionsManager.OPTIONS_SETTINGS['start_minimized']['items'][0] + main_window = widgets["main_window"] + start_minimized = widgets["start_minimized"].currentText() == OptionsManager.OPTIONS_SETTINGS["start_minimized"]["items"][0] main_window.start_minimized = start_minimized @staticmethod @@ -298,8 +305,8 @@ def apply_start_maximized_options(widgets): """ Apply the start maximized option to the application. """ - main_window = widgets['main_window'] - start_maximized = widgets['start_maximized'].currentText() == OptionsManager.OPTIONS_SETTINGS['start_maximized']['items'][0] + main_window = widgets["main_window"] + start_maximized = widgets["start_maximized"].currentText() == OptionsManager.OPTIONS_SETTINGS["start_maximized"]["items"][0] main_window.start_maximized = start_maximized @staticmethod @@ -307,8 +314,8 @@ def apply_welcome_message_options(widgets): """ Apply the welcome message option to the application. """ - main_window = widgets['main_window'] - show_welcome = widgets['welcome_message'].currentText() == OptionsManager.OPTIONS_SETTINGS['welcome_message']['items'][0] + main_window = widgets["main_window"] + show_welcome = widgets["welcome_message"].currentText() == OptionsManager.OPTIONS_SETTINGS["welcome_message"]["items"][0] main_window.show_welcome = show_welcome @staticmethod @@ -316,18 +323,27 @@ def apply_check_updates_options(widgets): """ Apply the check updates option to the application. """ - main_window = widgets['main_window'] - check_updates = widgets['check_updates'].currentText() == OptionsManager.OPTIONS_SETTINGS['check_updates']['items'][0] + main_window = widgets["main_window"] + check_updates = widgets["check_updates"].currentText() == OptionsManager.OPTIONS_SETTINGS["check_updates"]["items"][0] main_window.check_updates = check_updates + @staticmethod + def apply_volt_path_options(widgets): + """ + Apply the volt path option to the application. + """ + main_window = widgets["main_window"] + volt_path = widgets["volt_path"].currentText() + main_window.volt_path = volt_path + @staticmethod def apply_scaling_options(widgets): """ Apply interface scaling options to the application. """ - main_window = widgets['main_window'] - scaling_factor = float(widgets['scaling'].currentText()) - os.environ['QT_SCALE_FACTOR'] = str(scaling_factor) + main_window = widgets["main_window"] + scaling_factor = float(widgets["scaling"].currentText()) + os.environ["QT_SCALE_FACTOR"] = str(scaling_factor) main_window.scaling_factor = scaling_factor @staticmethod @@ -335,27 +351,34 @@ def get_welcome_message_setting(widgets): """ Get the current welcome message setting. """ - return widgets['welcome_message'].currentText() == OptionsManager.OPTIONS_SETTINGS['welcome_message']['items'][0] + return widgets["welcome_message"].currentText() == OptionsManager.OPTIONS_SETTINGS["welcome_message"]["items"][0] @staticmethod def get_check_updates_setting(widgets): """ Get the current check updates setting. """ - return widgets['check_updates'].currentText() == OptionsManager.OPTIONS_SETTINGS['check_updates']['items'][0] + return widgets["check_updates"].currentText() == OptionsManager.OPTIONS_SETTINGS["check_updates"]["items"][0] + + @staticmethod + def get_volt_path_setting(widgets): + """ + Get the current volt path. + """ + return widgets["volt_path"].currentText() @staticmethod def save_and_apply_options(widgets): """ Save current options and apply them to the application. """ - main_window = widgets['main_window'] + main_window = widgets["main_window"] - old_scaling = getattr(main_window, 'scaling_factor', 1.0) - new_scaling = float(widgets['scaling'].currentText()) + old_scaling = getattr(main_window, "scaling_factor", 1.0) + new_scaling = float(widgets["scaling"].currentText()) scaling_changed = old_scaling != new_scaling - widgets['options_apply_button'].setEnabled(False) + widgets["options_apply_button"].setEnabled(False) try: OptionsManager.save_options(widgets) @@ -364,18 +387,18 @@ def save_and_apply_options(widgets): if scaling_changed: message += ".\nInterface scaling will take full effect after restarting the application." - if hasattr(main_window, 'tray_icon'): + if hasattr(main_window, "tray_icon"): main_window.tray_icon.showMessage("volt-gui", message, main_window.tray_icon.MessageIcon.Information, 3000) else: QMessageBox.information(main_window, "volt-gui", message) - QTimer.singleShot(1000, lambda: widgets['options_apply_button'].setEnabled(True)) + QTimer.singleShot(1000, lambda: widgets["options_apply_button"].setEnabled(True)) except Exception as e: error_message = f"Failed to save options: {str(e)}" - if hasattr(main_window, 'tray_icon'): + if hasattr(main_window, "tray_icon"): main_window.tray_icon.showMessage("volt-gui", error_message, main_window.tray_icon.MessageIcon.Critical, 3000) else: QMessageBox.warning(main_window, "volt-gui", error_message) - widgets['options_apply_button'].setEnabled(True) + widgets["options_apply_button"].setEnabled(True) diff --git a/src/script_helper.py b/src/script_helper.py new file mode 100644 index 0000000..96c718d --- /dev/null +++ b/src/script_helper.py @@ -0,0 +1,181 @@ +import os, stat + +class HelperManager: + """ + Manages the creation of the volt-helper script. + """ + + BASH_SCRIPT_CONTENT = r"""#!/bin/bash + +set -euo pipefail + +check_commands() { + for cmd in pgrep pkill sleep chmod grep cut; do + if ! command -v "$cmd" &> /dev/null; then + echo "Error: Required command \"$cmd\" not found" >&2 + exit 1 + fi + done +} + +terminate_schedulers() { + pgrep -f "^scx_" >/dev/null 2>&1 && pkill -INT -f "^scx_" && sleep 0.5 + pgrep -f "^scx_" >/dev/null 2>&1 && pkill -TERM -f "^scx_" && sleep 0.5 + pgrep -f "^scx_" >/dev/null 2>&1 && pkill -KILL -f "^scx_" && sleep 0.2 + return 0 +} + +apply_cpu() { + local governor="" + local scheduler="" + local max_freq="" + local min_freq="" + local has_scheduler=false + + for arg in "$@"; do + case "$arg" in + governor:*) governor="${arg#governor:}" ;; + max_freq:*) max_freq="${arg#max_freq:}" ;; + min_freq:*) min_freq="${arg#min_freq:}" ;; + scheduler:*) + scheduler="${arg#scheduler:}" + has_scheduler=true + ;; + esac + done + + if [[ -n "$governor" ]]; then + for path in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do + echo "$governor" > "$path" + done + fi + + if [[ -n "$min_freq" ]]; then + for path in /sys/devices/system/cpu/cpu*/cpufreq/scaling_min_freq; do + echo "$min_freq" > "$path" + done + fi + + if [[ -n "$max_freq" ]]; then + for path in /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq; do + echo "$max_freq" > "$path" + done + fi + + terminate_schedulers + + if [[ "$has_scheduler" == true && "$scheduler" != "none" ]]; then + "$scheduler" & + sleep 1 + fi +} + +apply_disk() { + for arg in "$@"; do + [[ "$arg" == *":"* ]] && echo "${arg#*:}" > "/sys/block/${arg%%:*}/queue/scheduler" + done +} + +apply_kernel() { + for arg in "$@"; do + echo "${arg#*:}" > "${arg%%:*}" + done +} + +apply_gpu() { + local settings_file="$1" + local volt_path="$2" + local script_content="#!/bin/bash\n\n" + + while IFS="=" read -r key value; do + [[ -z "$key" || "$key" =~ ^[[:space:]]*# ]] && continue + + key="${key// /}" + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + + if [[ "$key" == "launch_options" ]]; then + continue + elif [[ -n "$value" ]]; then + script_content+="export ${key}=\"${value}\"\n" + elif [[ "$key" == unset:* ]]; then + script_content+="unset ${key#unset:}\n" + fi + done < "$settings_file" + + script_content+="\n" + + if grep -q "^launch_options=" "$settings_file" 2>/dev/null; then + local launch_opts="" + launch_opts="$(grep "^launch_options=" "$settings_file" | cut -d"=" -f2-)" + launch_opts="${launch_opts#"${launch_opts%%[![:space:]]*}"}" + launch_opts="${launch_opts%"${launch_opts##*[![:space:]]}"}" + script_content+="${launch_opts} \"\$@\"\n" + else + script_content+="\"\$@\"\n" + fi + + echo -e "$script_content" > "$volt_path" + chmod 755 "$volt_path" +} + +main() { + check_commands + local volt_path="" + + while [[ $# -gt 0 ]]; do + case "$1" in + -c|--cpu) + shift + local cpu_args=() + while [[ $# -gt 0 && "$1" != -* ]]; do + cpu_args+=("$1") + shift + done + apply_cpu "${cpu_args[@]}" + ;; + -d|--disk) + shift + local disk_args=() + while [[ $# -gt 0 && "$1" != -* ]]; do + disk_args+=("$1") + shift + done + apply_disk "${disk_args[@]}" + ;; + -k|--kernel) + shift + local kernel_args=() + while [[ $# -gt 0 && "$1" != -* ]]; do + kernel_args+=("$1") + shift + done + apply_kernel "${kernel_args[@]}" + ;; + -p|--path) + volt_path="$2" + shift 2 + ;; + -g|--gpu) + apply_gpu "$2" "$volt_path" + shift 2 + ;; + *) + shift + ;; + esac + done +} + +main "$@" +""" + + @staticmethod + def create_helper_script(): + """ + Creates the volt-helper bash script in /tmp with executable permissions. + """ + with open("/tmp/volt-helper", "w") as f: + f.write(HelperManager.BASH_SCRIPT_CONTENT) + + os.chmod("/tmp/volt-helper", stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) diff --git a/src/theme.py b/src/theme.py index 1fa45be..f46b3ea 100644 --- a/src/theme.py +++ b/src/theme.py @@ -3,45 +3,45 @@ class ThemeManager: AMD_COLORS = { - 'bg_color': "#1A1A1A", - 'darker_bg': "#0F0F0F", - 'lighter_bg': "#252525", - 'surface_bg': "#202020", - 'text_color': "#FFFFFF", - 'text_secondary': "#B0B0B0", - 'accent_color': "#FF0000", - 'accent_hover': "#FF3333", - 'accent_pressed': "#CC0000", - 'disabled_text': "#666666", - 'selection_bg': "#FF0000", + "bg_color": "#1A1A1A", + "darker_bg": "#0F0F0F", + "lighter_bg": "#252525", + "surface_bg": "#202020", + "text_color": "#FFFFFF", + "text_secondary": "#B0B0B0", + "accent_color": "#FF0000", + "accent_hover": "#FF3333", + "accent_pressed": "#CC0000", + "disabled_text": "#666666", + "selection_bg": "#FF0000", } INTEL_COLORS = { - 'bg_color': "#1A1A1A", - 'darker_bg': "#0F0F0F", - 'lighter_bg': "#252525", - 'surface_bg': "#202020", - 'text_color': "#FFFFFF", - 'text_secondary': "#B0B0B0", - 'accent_color': "#0071C5", - 'accent_hover': "#3399FF", - 'accent_pressed': "#004D87", - 'disabled_text': "#666666", - 'selection_bg': "#0071C5", + "bg_color": "#1A1A1A", + "darker_bg": "#0F0F0F", + "lighter_bg": "#252525", + "surface_bg": "#202020", + "text_color": "#FFFFFF", + "text_secondary": "#B0B0B0", + "accent_color": "#0071C5", + "accent_hover": "#3399FF", + "accent_pressed": "#004D87", + "disabled_text": "#666666", + "selection_bg": "#0071C5", } NVIDIA_COLORS = { - 'bg_color': "#1A1A1A", - 'darker_bg': "#0F0F0F", - 'lighter_bg': "#252525", - 'surface_bg': "#202020", - 'text_color': "#FFFFFF", - 'text_secondary': "#B0B0B0", - 'accent_color': "#76B900", - 'accent_hover': "#9AE62C", - 'accent_pressed': "#5A8A00", - 'disabled_text': "#666666", - 'selection_bg': "#76B900", + "bg_color": "#1A1A1A", + "darker_bg": "#0F0F0F", + "lighter_bg": "#252525", + "surface_bg": "#202020", + "text_color": "#FFFFFF", + "text_secondary": "#B0B0B0", + "accent_color": "#76B900", + "accent_hover": "#9AE62C", + "accent_pressed": "#5A8A00", + "disabled_text": "#666666", + "selection_bg": "#76B900", } THEMES = { @@ -66,15 +66,15 @@ def get_theme_style_sheet(cls): c = cls.COLORS return f""" QWidget {{ - background-color: {c['bg_color']}; - color: {c['text_color']}; + background-color: {c["bg_color"]}; + color: {c["text_color"]}; font-size: 10pt; font-family: "Segoe UI", sans-serif; border-radius: 3px; }} QLabel {{ - color: {c['text_color']}; + color: {c["text_color"]}; background-color: transparent; border: none; border-radius: 3px; @@ -101,46 +101,46 @@ def get_theme_style_sheet(cls): QWidget[buttonContainer="true"] {{ min-height: 50px; - background-color: {c['bg_color']}; + background-color: {c["bg_color"]}; border-radius: 3px; }} QMainWindow {{ - background-color: {c['bg_color']}; + background-color: {c["bg_color"]}; border: none; border-radius: 3px; }} QTabBar::tab:hover:!selected {{ - background-color: {c['lighter_bg']}; - color: {c['text_color']}; - border: 1px solid {c['accent_color']}; + background-color: {c["lighter_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; }} QTabBar::scroller {{ width: 30px; - background-color: {c['surface_bg']}; + background-color: {c["surface_bg"]}; border: none; border-radius: 3px; }} QTabBar QToolButton {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; border: none; border-radius: 3px; padding: 4px; }} QTabBar QToolButton:hover {{ - background-color: {c['lighter_bg']}; + background-color: {c["lighter_bg"]}; border-radius: 3px; - border: 1px solid {c['accent_color']}; + border: 1px solid {c["accent_color"]}; }} QTabBar QToolButton:pressed {{ - background-color: {c['accent_pressed']}; + background-color: {c["accent_pressed"]}; border-radius: 3px; }} @@ -152,27 +152,27 @@ def get_theme_style_sheet(cls): }} QTabWidget {{ - background-color: {c['bg_color']}; + background-color: {c["bg_color"]}; border: none; border-radius: 3px; }} QTabWidget::pane {{ - background-color: {c['bg_color']}; + background-color: {c["bg_color"]}; border: none; border-radius: 3px; }} QTabBar {{ - background-color: {c['bg_color']}; + background-color: {c["bg_color"]}; qproperty-drawBase: 0; border: none; border-radius: 3px; }} QTabBar::tab {{ - background-color: {c['bg_color']}; - color: {c['text_secondary']}; + background-color: {c["bg_color"]}; + color: {c["text_secondary"]}; border: none; border-radius: 3px; padding: 12px 24px; @@ -181,18 +181,18 @@ def get_theme_style_sheet(cls): }} QTabBar::tab:selected {{ - background-color: {c['bg_color']}; - color: {c['text_color']}; - border-left: 3px solid {c['accent_color']}; + background-color: {c["bg_color"]}; + color: {c["text_color"]}; + border-left: 3px solid {c["accent_color"]}; border-radius: 0px 3px 3px 0px; padding-left: 21px; }} QTabBar::tab:hover:!selected {{ - background-color: {c['lighter_bg']}; - color: {c['text_color']}; + background-color: {c["lighter_bg"]}; + color: {c["text_color"]}; border-radius: 3px; - border: 1px solid {c['accent_color']}; + border: 1px solid {c["accent_color"]}; }} QScrollBar:vertical {{ @@ -204,15 +204,15 @@ def get_theme_style_sheet(cls): }} QScrollBar::handle:vertical {{ - background: {c['text_secondary']}; + background: {c["text_secondary"]}; min-height: 30px; border: none; border-radius: 3px; }} QScrollBar::handle:vertical:hover {{ - background: {c['accent_color']}; - border: 1px solid {c['accent_hover']}; + background: {c["accent_color"]}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; }} @@ -235,15 +235,15 @@ def get_theme_style_sheet(cls): }} QScrollBar::handle:horizontal {{ - background: {c['text_secondary']}; + background: {c["text_secondary"]}; min-width: 30px; border: none; border-radius: 3px; }} QScrollBar::handle:horizontal:hover {{ - background: {c['accent_color']}; - border: 1px solid {c['accent_hover']}; + background: {c["accent_color"]}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; }} @@ -258,9 +258,9 @@ def get_theme_style_sheet(cls): }} QPushButton {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; outline: none; padding: 10px 16px; @@ -269,48 +269,48 @@ def get_theme_style_sheet(cls): }} QPushButton:disabled {{ - background-color: {c['darker_bg']}; - color: {c['disabled_text']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["darker_bg"]}; + color: {c["disabled_text"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; }} QPushButton:default {{ - background-color: {c['accent_color']}; + background-color: {c["accent_color"]}; color: white; - border: 1px solid {c['accent_hover']}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; outline: none; }} QPushButton:hover {{ - background-color: {c['lighter_bg']}; - border: 1px solid {c['accent_color']}; + background-color: {c["lighter_bg"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; outline: none; }} QPushButton:default:hover {{ - background-color: {c['accent_hover']}; - border: 1px solid {c['accent_hover']}; + background-color: {c["accent_hover"]}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; outline: none; }} QPushButton:pressed {{ - background-color: {c['accent_pressed']}; - border: 1px solid {c['accent_pressed']}; + background-color: {c["accent_pressed"]}; + border: 1px solid {c["accent_pressed"]}; border-radius: 3px; outline: none; }} QComboBox, QSpinBox, QDoubleSpinBox, QLineEdit {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; padding: 10px 12px; - selection-background-color: {c['selection_bg']}; + selection-background-color: {c["selection_bg"]}; selection-color: white; }} @@ -320,21 +320,21 @@ def get_theme_style_sheet(cls): }} QComboBox:hover, QSpinBox:hover, QDoubleSpinBox:hover, QLineEdit:hover {{ - background-color: {c['lighter_bg']}; - border: 1px solid {c['accent_color']}; + background-color: {c["lighter_bg"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; }} QComboBox:focus, QSpinBox:focus, QDoubleSpinBox:focus, QLineEdit:focus {{ - background-color: {c['lighter_bg']}; - border: 1px solid {c['accent_color']}; + background-color: {c["lighter_bg"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; }} QComboBox:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QLineEdit:disabled {{ - background-color: {c['darker_bg']}; - color: {c['disabled_text']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["darker_bg"]}; + color: {c["disabled_text"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; }} @@ -348,11 +348,11 @@ def get_theme_style_sheet(cls): }} QComboBox QAbstractItemView {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['accent_color']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; - selection-background-color: {c['accent_color']}; + selection-background-color: {c["accent_color"]}; selection-color: white; outline: none; }} @@ -367,14 +367,14 @@ def get_theme_style_sheet(cls): QSpinBox::up-button:hover, QDoubleSpinBox::up-button:hover, QSpinBox::down-button:hover, QDoubleSpinBox::down-button:hover {{ - background-color: {c['accent_color']}; - border: 1px solid {c['accent_hover']}; + background-color: {c["accent_color"]}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; }} QLabel[isHeader="true"] {{ font-weight: 600; - color: {c['accent_color']}; + color: {c["accent_color"]}; font-size: 13pt; padding: 12px 0px 8px 0px; background-color: transparent; @@ -383,7 +383,7 @@ def get_theme_style_sheet(cls): }} QCheckBox {{ - color: {c['text_color']}; + color: {c["text_color"]}; spacing: 10px; border: none; border-radius: 3px; @@ -392,40 +392,40 @@ def get_theme_style_sheet(cls): QCheckBox::indicator {{ width: 20px; height: 20px; - background-color: {c['surface_bg']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["surface_bg"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; }} QCheckBox::indicator:unchecked:hover {{ - background-color: {c['lighter_bg']}; - border: 1px solid {c['accent_color']}; + background-color: {c["lighter_bg"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; }} QCheckBox::indicator:checked {{ - background-color: {c['accent_color']}; - border: 1px solid {c['accent_hover']}; + background-color: {c["accent_color"]}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; }} QCheckBox::indicator:checked:hover {{ - background-color: {c['accent_hover']}; - border: 1px solid {c['accent_hover']}; + background-color: {c["accent_hover"]}; + border: 1px solid {c["accent_hover"]}; border-radius: 3px; }} QWidget[statusContainer="true"] {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; padding: 8px; }} QWidget[statusContainer="true"] QLabel {{ background-color: transparent; - color: {c['text_color']}; + color: {c["text_color"]}; padding: 4px; border: none; border-radius: 3px; @@ -433,8 +433,8 @@ def get_theme_style_sheet(cls): QGroupBox {{ background-color: transparent; - color: {c['text_color']}; - border: 1px solid {c['darker_bg']}; + color: {c["text_color"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; font-weight: 600; font-size: 11pt; @@ -442,7 +442,7 @@ def get_theme_style_sheet(cls): }} QGroupBox::title {{ - color: {c['accent_color']}; + color: {c["accent_color"]}; subcontrol-origin: margin; left: 0px; padding: 0px 0px 8px 0px; @@ -450,15 +450,15 @@ def get_theme_style_sheet(cls): }} QFrame#profileFrame {{ - border: 1px solid {c['accent_color']}; - background-color: {c['bg_color']}; + border: 1px solid {c["accent_color"]}; + background-color: {c["bg_color"]}; border-radius: 0px; }} QMenu {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['accent_color']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["accent_color"]}; border-radius: 3px; padding: 4px; }} @@ -471,37 +471,37 @@ def get_theme_style_sheet(cls): }} QMenu::item:selected {{ - background-color: {c['accent_color']}; + background-color: {c["accent_color"]}; color: white; border-radius: 3px; }} QMenu::separator {{ height: 1px; - background-color: {c['lighter_bg']}; + background-color: {c["lighter_bg"]}; margin: 4px 0px; border-radius: 3px; }} QProgressBar {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['darker_bg']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["darker_bg"]}; border-radius: 3px; text-align: center; padding: 2px; }} QProgressBar::chunk {{ - background-color: {c['accent_color']}; + background-color: {c["accent_color"]}; border: none; border-radius: 3px; }} QToolTip {{ - background-color: {c['surface_bg']}; - color: {c['text_color']}; - border: 1px solid {c['accent_color']}; + background-color: {c["surface_bg"]}; + color: {c["text_color"]}; + border: 1px solid {c["accent_color"]}; border-radius: 5px; padding: 8px 12px; font-size: 10pt; @@ -519,24 +519,24 @@ def get_theme_palette(cls): palette = QPalette() c = cls.COLORS - palette.setColor(QPalette.Window, QColor(c['bg_color'])) - palette.setColor(QPalette.WindowText, QColor(c['text_color'])) - palette.setColor(QPalette.Base, QColor(c['surface_bg'])) - palette.setColor(QPalette.AlternateBase, QColor(c['lighter_bg'])) - palette.setColor(QPalette.Text, QColor(c['text_color'])) + palette.setColor(QPalette.Window, QColor(c["bg_color"])) + palette.setColor(QPalette.WindowText, QColor(c["text_color"])) + palette.setColor(QPalette.Base, QColor(c["surface_bg"])) + palette.setColor(QPalette.AlternateBase, QColor(c["lighter_bg"])) + palette.setColor(QPalette.Text, QColor(c["text_color"])) palette.setColor(QPalette.BrightText, QColor("#FFFFFF")) - palette.setColor(QPalette.Button, QColor(c['surface_bg'])) - palette.setColor(QPalette.ButtonText, QColor(c['text_color'])) - palette.setColor(QPalette.Highlight, QColor(c['accent_color'])) + palette.setColor(QPalette.Button, QColor(c["surface_bg"])) + palette.setColor(QPalette.ButtonText, QColor(c["text_color"])) + palette.setColor(QPalette.Highlight, QColor(c["accent_color"])) palette.setColor(QPalette.HighlightedText, QColor("#FFFFFF")) - palette.setColor(QPalette.Disabled, QPalette.Text, QColor(c['disabled_text'])) - palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(c['disabled_text'])) - palette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(c['disabled_text'])) - palette.setColor(QPalette.Disabled, QPalette.Window, QColor(c['darker_bg'])) - palette.setColor(QPalette.Disabled, QPalette.Base, QColor(c['darker_bg'])) - palette.setColor(QPalette.Disabled, QPalette.Button, QColor(c['darker_bg'])) - palette.setColor(QPalette.ToolTipBase, QColor(c['surface_bg'])) - palette.setColor(QPalette.ToolTipText, QColor(c['text_color'])) + palette.setColor(QPalette.Disabled, QPalette.Text, QColor(c["disabled_text"])) + palette.setColor(QPalette.Disabled, QPalette.ButtonText, QColor(c["disabled_text"])) + palette.setColor(QPalette.Disabled, QPalette.WindowText, QColor(c["disabled_text"])) + palette.setColor(QPalette.Disabled, QPalette.Window, QColor(c["darker_bg"])) + palette.setColor(QPalette.Disabled, QPalette.Base, QColor(c["darker_bg"])) + palette.setColor(QPalette.Disabled, QPalette.Button, QColor(c["darker_bg"])) + palette.setColor(QPalette.ToolTipBase, QColor(c["surface_bg"])) + palette.setColor(QPalette.ToolTipText, QColor(c["text_color"])) return palette diff --git a/src/update_checker.py b/src/update_checker.py index afc5586..4a12347 100644 --- a/src/update_checker.py +++ b/src/update_checker.py @@ -37,12 +37,12 @@ def show_update_notification(main_window, new_version): """ message = f"A new volt-gui version is available: {new_version}" - if hasattr(main_window, 'tray_icon') and main_window.use_system_tray: + if hasattr(main_window, "tray_icon") and main_window.use_system_tray: main_window.tray_icon.showMessage( - "volt-gui Update", + "volt-gui", message, main_window.tray_icon.MessageIcon.Information, 5000 ) else: - QMessageBox.information(main_window, "volt-gui Update", message) + QMessageBox.information(main_window, "volt-gui", message) diff --git a/src/version.py b/src/version.py index 7b49cf1..4cf03a8 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -VERSION = "1.3.0" +VERSION = "1.3.1" diff --git a/src/volt-gui.py b/src/volt-gui.py index 21cfdc8..01e710b 100644 --- a/src/volt-gui.py +++ b/src/volt-gui.py @@ -14,12 +14,13 @@ from workarounds import WorkaroundManager from welcome import WelcomeManager from update_checker import UpdateChecker +from script_helper import HelperManager def check_sudo_execution(): """ Check if the application is run with sudo and exit if it is. """ - if os.environ.get('SUDO_USER'): + if os.environ.get("SUDO_USER"): print("Error: This application should not be run with sudo.") print("Please run as a regular user. The application will request") print("elevated privileges when needed through pkexec.") @@ -37,7 +38,7 @@ def __init__(self, port=47832): """ self.port = port self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.server_address = ('localhost', self.port) + self.server_address = ("localhost", self.port) self.signals = SingletonSignals() self.listener_thread = None self.running = False @@ -131,6 +132,7 @@ def __init__(self, instance_checker): self.use_system_tray = False self.start_minimized = False self.start_maximized = False + self.volt_path = "/usr/local/bin/volt" self.current_profile = "Default" self.cpu_widgets = {} self.kernel_widgets = {} @@ -248,7 +250,7 @@ def setup_cpu_tab(self): Set up the CPU management tab. """ cpu_tab, self.cpu_widgets = CPUManager.create_cpu_tab() - self.cpu_widgets['cpu_apply_button'].clicked.connect(self.apply_all_settings) + self.cpu_widgets["cpu_apply_button"].clicked.connect(self.apply_all_settings) CPUManager.refresh_cpu_values(self.cpu_widgets) self.tab_widget.addTab(cpu_tab, "CPU") @@ -257,7 +259,7 @@ def setup_disk_tab(self): Set up the disk management tab. """ disk_tab, self.disk_widgets = DiskManager.create_disk_tab() - self.disk_widgets['disk_apply_button'].clicked.connect(self.apply_all_settings) + self.disk_widgets["disk_apply_button"].clicked.connect(self.apply_all_settings) DiskManager.refresh_disk_values(self.disk_widgets) self.tab_widget.addTab(disk_tab, "Disk") @@ -268,7 +270,7 @@ def setup_gpu_tabs(self): gpu_tab, self.gpu_widgets = GPULaunchManager.create_gpu_settings_tabs() for category_name, category_widgets in self.gpu_widgets.items(): - if category_name != 'LaunchOptions': + if category_name != "LaunchOptions": apply_button_name = f"{category_name.lower()}_apply_button" if apply_button_name in category_widgets: category_widgets[apply_button_name].clicked.connect(self.apply_all_settings) @@ -280,8 +282,8 @@ def setup_launch_options_tab(self): Set up the launch options tab. """ launch_options_tab, self.launch_options_widgets = GPULaunchManager.create_launch_options_tab() - self.gpu_widgets['LaunchOptions'] = self.launch_options_widgets - self.launch_options_widgets['launch_apply_button'].clicked.connect(self.apply_all_settings) + self.gpu_widgets["LaunchOptions"] = self.launch_options_widgets + self.launch_options_widgets["launch_apply_button"].clicked.connect(self.apply_all_settings) self.tab_widget.addTab(launch_options_tab, "Launch Options") def setup_kernel_tab(self): @@ -289,10 +291,10 @@ def setup_kernel_tab(self): Set up the kernel management tab. """ kernel_tab, self.kernel_widgets = KernelManager.create_kernel_tab(self) - self.kernel_widgets['kernel_apply_button'].clicked.connect(self.apply_all_settings) + self.kernel_widgets["kernel_apply_button"].clicked.connect(self.apply_all_settings) for setting_name in KernelManager.KERNEL_SETTINGS.keys(): - widget_key = f'{setting_name}_input' + widget_key = f"{setting_name}_input" self.kernel_widgets[widget_key].installEventFilter(self) KernelManager.refresh_kernel_values(self.kernel_widgets) @@ -310,7 +312,7 @@ def setup_options_tab(self): Set up the options tab. """ options_tab, self.options_widgets = OptionsManager.create_options_tab(self) - self.options_widgets['options_apply_button'].clicked.connect(lambda: OptionsManager.save_and_apply_options(self.options_widgets)) + self.options_widgets["options_apply_button"].clicked.connect(lambda: OptionsManager.save_and_apply_options(self.options_widgets)) self.tab_widget.addTab(options_tab, "Options") def setup_about_tab(self): @@ -376,7 +378,7 @@ def update_tray_profile_menu(self): """ Update the tray profile menu with available profiles. """ - if not hasattr(self, 'profile_submenu'): + if not hasattr(self, "profile_submenu"): return self.profile_submenu.clear() @@ -451,7 +453,7 @@ def on_profile_changed(self, profile_name): if not profile_name or profile_name == self.current_profile or profile_name.isspace(): return - if hasattr(self, '_initial_setup_complete') and self._initial_setup_complete: + if hasattr(self, "_initial_setup_complete") and self._initial_setup_complete: try: ConfigManager.save_config( self.cpu_widgets, @@ -510,7 +512,7 @@ def save_new_profile(self): self.update_profile_list() self.profile_selector.setCurrentText(profile_name) - if hasattr(self, 'tray_icon') and self.use_system_tray: + if hasattr(self, "tray_icon") and self.use_system_tray: self.update_tray_profile_menu() QMessageBox.information(self, "Profile Saved", f"Profile '{profile_name}' has been saved.") @@ -554,7 +556,7 @@ def delete_current_profile(self): self.refresh_disk_values() self.refresh_kernel_values() - if hasattr(self, 'tray_icon') and self.use_system_tray: + if hasattr(self, "tray_icon") and self.use_system_tray: self.update_tray_profile_menu() QMessageBox.information(self, "Profile Deleted", f"Profile '{current_profile}' has been deleted.") @@ -629,28 +631,28 @@ def apply_all_settings(self): Collects all settings (CPU, Disk, GPU, Kernel) and applies them in one go using volt-helper. Uses clean environment to avoid PyInstaller interference. """ - if (self.cpu_widgets.get('is_process_running', False) or - self.disk_widgets.get('is_process_running', False) or - self.kernel_widgets.get('is_process_running', False)): + if (self.cpu_widgets.get("is_process_running", False) or + self.disk_widgets.get("is_process_running", False) or + self.kernel_widgets.get("is_process_running", False)): return try: self.save_settings() cpu_args = [] - cpu_governor = self.cpu_widgets['scaling_governor'].currentText() + cpu_governor = self.cpu_widgets["scaling_governor"].currentText() - if 'max_freq' in self.cpu_widgets: - cpu_max_freq = self.cpu_widgets['scaling_max_freq'].currentText() + if "scaling_max_freq" in self.cpu_widgets: + cpu_max_freq = self.cpu_widgets["scaling_max_freq"].currentText() else: cpu_max_freq = "unset" - if 'min_freq' in self.cpu_widgets: - cpu_min_freq = self.cpu_widgets['scaling_min_freq'].currentText() + if "scaling_min_freq" in self.cpu_widgets: + cpu_min_freq = self.cpu_widgets["scaling_min_freq"].currentText() else: cpu_min_freq = "unset" - cpu_scheduler = self.cpu_widgets['scheduler'].currentText() + cpu_scheduler = self.cpu_widgets["scheduler"].currentText() cpu_parts = [] if cpu_governor != "unset": @@ -672,8 +674,8 @@ def apply_all_settings(self): cpu_args.extend(cpu_parts) disk_args = [] - for disk_name, disk_widgets in self.disk_widgets['disk_settings'].items(): - selected_scheduler = disk_widgets['scheduler'].currentText() + for disk_name, disk_widgets in self.disk_widgets["disk_settings"].items(): + selected_scheduler = disk_widgets["scheduler"].currentText() if selected_scheduler and selected_scheduler != "" and selected_scheduler != "unset": if not disk_args: disk_args.append("-d") @@ -682,7 +684,7 @@ def apply_all_settings(self): kernel_args = [] for category in KernelManager.KERNEL_SETTINGS_CATEGORIES.values(): for name, info in category.items(): - value = self.kernel_widgets[f'{name}_input'].text().strip() + value = self.kernel_widgets[f"{name}_input"].text().strip() if value: if not kernel_args: kernel_args.append("-k") @@ -690,17 +692,18 @@ def apply_all_settings(self): gpu_args = [] settings_file = GPULaunchManager.write_settings_file( - self.gpu_widgets['Mesa'], - self.gpu_widgets['NVIDIA'], - self.gpu_widgets['RenderSelector'], - self.gpu_widgets['MangoHud'], - self.gpu_widgets['LSFrameGen'], - self.gpu_widgets['LaunchOptions'] + self.gpu_widgets["Mesa"], + self.gpu_widgets["NVIDIA"], + self.gpu_widgets["RenderSelector"], + self.gpu_widgets["MangoHud"], + self.gpu_widgets["LSFrameGen"], + self.gpu_widgets["LaunchOptions"] ) if settings_file: - gpu_args.extend(["-g", settings_file]) + gpu_args.extend(["-p", self.volt_path, "-g", settings_file]) - all_args = ["pkexec", "/usr/local/bin/volt-helper"] + cpu_args + disk_args + kernel_args + gpu_args + HelperManager.create_helper_script() + all_args = ["pkexec", "/tmp/volt-helper"] + cpu_args + disk_args + kernel_args + gpu_args process = QProcess() WorkaroundManager.setup_clean_process(process) @@ -708,21 +711,21 @@ def apply_all_settings(self): process.start(all_args[0], all_args[1:]) process.finished.connect(lambda: self.on_settings_applied(process.exitCode())) - self.cpu_widgets['cpu_apply_button'].setEnabled(False) - self.disk_widgets['disk_apply_button'].setEnabled(False) - self.kernel_widgets['kernel_apply_button'].setEnabled(False) + self.cpu_widgets["cpu_apply_button"].setEnabled(False) + self.disk_widgets["disk_apply_button"].setEnabled(False) + self.kernel_widgets["kernel_apply_button"].setEnabled(False) for category_name, category_widgets in self.gpu_widgets.items(): - if category_name != 'LaunchOptions': + if category_name != "LaunchOptions": apply_button_name = f"{category_name.lower()}_apply_button" if apply_button_name in category_widgets: category_widgets[apply_button_name].setEnabled(False) - self.launch_options_widgets['launch_apply_button'].setEnabled(False) + self.launch_options_widgets["launch_apply_button"].setEnabled(False) except Exception as e: print(f"Error applying settings: {e}") - if hasattr(self, 'tray_icon'): + if hasattr(self, "tray_icon"): self.tray_icon.showMessage("volt-gui", f"Failed to apply settings: {e}", QSystemTrayIcon.MessageIcon.Critical, 2000) else: QMessageBox.warning(self, "volt-gui", f"Failed to apply settings: {e}") @@ -731,23 +734,23 @@ def on_settings_applied(self, exit_code): """ Handle the completion of the settings application process. """ - self.cpu_widgets['cpu_apply_button'].setEnabled(True) - self.disk_widgets['disk_apply_button'].setEnabled(True) - self.kernel_widgets['kernel_apply_button'].setEnabled(True) + self.cpu_widgets["cpu_apply_button"].setEnabled(True) + self.disk_widgets["disk_apply_button"].setEnabled(True) + self.kernel_widgets["kernel_apply_button"].setEnabled(True) for category_name, category_widgets in self.gpu_widgets.items(): - if category_name != 'LaunchOptions': + if category_name != "LaunchOptions": apply_button_name = f"{category_name.lower()}_apply_button" if apply_button_name in category_widgets: category_widgets[apply_button_name].setEnabled(True) - self.launch_options_widgets['launch_apply_button'].setEnabled(True) + self.launch_options_widgets["launch_apply_button"].setEnabled(True) self.refresh_cpu_values() self.refresh_disk_values() self.refresh_kernel_values() - if hasattr(self, 'tray_icon'): + if hasattr(self, "tray_icon"): self.tray_icon.showMessage("volt-gui", "Settings applied successfully" if exit_code == 0 else "Failed to apply settings", QSystemTrayIcon.MessageIcon.Information if exit_code == 0 else QSystemTrayIcon.MessageIcon.Critical, 2000) else: QMessageBox.information(self, "volt-gui", "Settings applied successfully") @@ -776,7 +779,7 @@ def closeEvent(self, event): Handle the main window close event. If system tray is enabled, hide to tray. If system tray is disabled, quit the application. """ - if self.use_system_tray and hasattr(self, 'tray_icon'): + if self.use_system_tray and hasattr(self, "tray_icon"): self.hide() event.ignore() else: diff --git a/src/welcome.py b/src/welcome.py index af59e79..77870e6 100644 --- a/src/welcome.py +++ b/src/welcome.py @@ -235,29 +235,29 @@ def create_navigation_buttons(parent_layout, widgets): nav_layout = QHBoxLayout() nav_layout.setContentsMargins(0, 16, 0, 0) - widgets['back_button'] = QPushButton("← Back") - widgets['back_button'].setMinimumHeight(32) - widgets['back_button'].setCursor(QCursor(Qt.PointingHandCursor)) - widgets['back_button'].setStyleSheet("QPushButton:disabled { color: #777777; }") + widgets["back_button"] = QPushButton("← Back") + widgets["back_button"].setMinimumHeight(32) + widgets["back_button"].setCursor(QCursor(Qt.PointingHandCursor)) + widgets["back_button"].setStyleSheet("QPushButton:disabled { color: #777777; }") - nav_layout.addWidget(widgets['back_button']) + nav_layout.addWidget(widgets["back_button"]) - widgets['progress_label'] = QLabel() - widgets['progress_label'].setAlignment(Qt.AlignCenter) - widgets['progress_label'].setStyleSheet("font-size: 13px; color: #888888; margin: 0 10px;") - nav_layout.addWidget(widgets['progress_label'], 1, Qt.AlignCenter) + widgets["progress_label"] = QLabel() + widgets["progress_label"].setAlignment(Qt.AlignCenter) + widgets["progress_label"].setStyleSheet("font-size: 13px; color: #888888; margin: 0 10px;") + nav_layout.addWidget(widgets["progress_label"], 1, Qt.AlignCenter) - widgets['next_button'] = QPushButton("Next →") - widgets['next_button'].setMinimumHeight(32) - widgets['next_button'].setCursor(QCursor(Qt.PointingHandCursor)) + widgets["next_button"] = QPushButton("Next →") + widgets["next_button"].setMinimumHeight(32) + widgets["next_button"].setCursor(QCursor(Qt.PointingHandCursor)) - widgets['finish_button'] = QPushButton("Finish") - widgets['finish_button'].setMinimumHeight(32) - widgets['finish_button'].setCursor(QCursor(Qt.PointingHandCursor)) - widgets['finish_button'].hide() + widgets["finish_button"] = QPushButton("Finish") + widgets["finish_button"].setMinimumHeight(32) + widgets["finish_button"].setCursor(QCursor(Qt.PointingHandCursor)) + widgets["finish_button"].hide() - nav_layout.addWidget(widgets['next_button']) - nav_layout.addWidget(widgets['finish_button']) + nav_layout.addWidget(widgets["next_button"]) + nav_layout.addWidget(widgets["finish_button"]) parent_layout.addLayout(nav_layout) @@ -266,24 +266,24 @@ def update_navigation(widgets): """ Updates the navigation buttons and progress indicator based on current step. """ - current_step = widgets['current_step'] + current_step = widgets["current_step"] total_steps = len(WelcomeManager.get_welcome_info()) - widgets['progress_label'].setText(f"Step {current_step + 1} of {total_steps}") - widgets['back_button'].setEnabled(current_step > 0) + widgets["progress_label"].setText(f"Step {current_step + 1} of {total_steps}") + widgets["back_button"].setEnabled(current_step > 0) is_last_step = current_step == total_steps - 1 - widgets['next_button'].setVisible(not is_last_step) - widgets['finish_button'].setVisible(is_last_step) + widgets["next_button"].setVisible(not is_last_step) + widgets["finish_button"].setVisible(is_last_step) @staticmethod def go_back(widgets): """ Go to the previous step. """ - if widgets['current_step'] > 0: - widgets['current_step'] -= 1 - widgets['stacked_widget'].setCurrentIndex(widgets['current_step']) + if widgets["current_step"] > 0: + widgets["current_step"] -= 1 + widgets["stacked_widget"].setCurrentIndex(widgets["current_step"]) WelcomeManager.update_navigation(widgets) @staticmethod @@ -292,9 +292,9 @@ def go_next(widgets): Go to the next step. """ total_steps = len(WelcomeManager.get_welcome_info()) - if widgets['current_step'] < total_steps - 1: - widgets['current_step'] += 1 - widgets['stacked_widget'].setCurrentIndex(widgets['current_step']) + if widgets["current_step"] < total_steps - 1: + widgets["current_step"] += 1 + widgets["stacked_widget"].setCurrentIndex(widgets["current_step"]) WelcomeManager.update_navigation(widgets) @staticmethod @@ -302,7 +302,7 @@ def finish_wizard(widgets): """ Finish the wizard and close the window. """ - welcome_window = widgets.get('welcome_window') + welcome_window = widgets.get("welcome_window") if welcome_window: welcome_window.close() @@ -321,21 +321,21 @@ def create_welcome_window(): main_layout.setSpacing(16) widgets = { - 'current_step': 0, - 'welcome_window': welcome_window, - 'stacked_widget': QStackedWidget() + "current_step": 0, + "welcome_window": welcome_window, + "stacked_widget": QStackedWidget() } for section in WelcomeManager.get_welcome_info(): page = WelcomeManager.create_step_page(section) - widgets['stacked_widget'].addWidget(page) + widgets["stacked_widget"].addWidget(page) - main_layout.addWidget(widgets['stacked_widget'], 1) + main_layout.addWidget(widgets["stacked_widget"], 1) WelcomeManager.create_navigation_buttons(main_layout, widgets) - widgets['back_button'].clicked.connect(lambda: WelcomeManager.go_back(widgets)) - widgets['next_button'].clicked.connect(lambda: WelcomeManager.go_next(widgets)) - widgets['finish_button'].clicked.connect(lambda: WelcomeManager.finish_wizard(widgets)) + widgets["back_button"].clicked.connect(lambda: WelcomeManager.go_back(widgets)) + widgets["next_button"].clicked.connect(lambda: WelcomeManager.go_next(widgets)) + widgets["finish_button"].clicked.connect(lambda: WelcomeManager.finish_wizard(widgets)) WelcomeManager.update_navigation(widgets) welcome_window.setCentralWidget(central_widget) diff --git a/src/workarounds.py b/src/workarounds.py index 2a3f405..1d436f7 100644 --- a/src/workarounds.py +++ b/src/workarounds.py @@ -9,8 +9,8 @@ def setup_qt_platform(): Default to X11 (xcb) Only sets if QT_QPA_PLATFORM is not already defined, allowing users to override if needed. """ - if 'QT_QPA_PLATFORM' not in os.environ: - os.environ['QT_QPA_PLATFORM'] = 'xcb' + if "QT_QPA_PLATFORM" not in os.environ: + os.environ["QT_QPA_PLATFORM"] = "xcb" @staticmethod def get_clean_env(): @@ -19,13 +19,13 @@ def get_clean_env(): Returns a list of strings in QProcess environment format. """ env = os.environ.copy() - if getattr(sys, 'frozen', False): - env.pop('LD_LIBRARY_PATH', None) - env.pop('LD_PRELOAD', None) - if hasattr(sys, '_MEIPASS') and 'PATH' in env: - paths = env['PATH'].split(os.pathsep) + if getattr(sys, "frozen", False): + env.pop("LD_LIBRARY_PATH", None) + env.pop("LD_PRELOAD", None) + if hasattr(sys, "_MEIPASS") and "PATH" in env: + paths = env["PATH"].split(os.pathsep) clean_paths = [p for p in paths if sys._MEIPASS not in p] - env['PATH'] = os.pathsep.join(clean_paths) + env["PATH"] = os.pathsep.join(clean_paths) return [f"{key}={value}" for key, value in env.items()] @staticmethod diff --git a/test.sh b/test.sh index 955bbe0..1116da8 100755 --- a/test.sh +++ b/test.sh @@ -2,26 +2,24 @@ set -euo pipefail -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -NC='\033[0m' - +RED="\033[0;31m" +BLUE="\033[0;34m" +NC="\033[0m" VENV_DIR="py_env" REQ_FILE="requirements.txt" REQ_HASH_FILE="$VENV_DIR/requirements.sha256" SRC_FILE="src/volt-gui.py" -HELPER_SCRIPT="scripts/volt-helper" -INSTALL_DIR="/usr/local/bin" -COPY_HELPER=false +cleanup() { + if [[ -n "${VIRTUAL_ENV:-}" ]]; then + deactivate 2>/dev/null || true + fi +} check_commands() { - local commands=("python3" "pip") - for cmd in "${commands[@]}"; do + for cmd in python3 pip shasum cut cat rm; do if ! command -v "$cmd" &> /dev/null; then - echo -e "${RED}Error: Required command '$cmd' not found${NC}" >&2 + echo -e "${RED}Error: Required command "$cmd" not found${NC}" >&2 exit 1 fi done @@ -29,7 +27,7 @@ check_commands() { create_venv() { if [[ ! -d "$VENV_DIR" ]]; then - echo -e "${CYAN}Creating python3 virtual environment...${NC}" + echo -e "${BLUE}Creating python3 virtual environment...${NC}" python3 -m venv "$VENV_DIR" fi } @@ -39,7 +37,6 @@ verify_files() { echo -e "${RED}Error: Requirements file $REQ_FILE not found${NC}" >&2 exit 1 fi - if [[ ! -f "$SRC_FILE" ]]; then echo -e "${RED}Error: Source file $SRC_FILE not found${NC}" >&2 exit 1 @@ -47,88 +44,66 @@ verify_files() { } update_dependencies() { - local current_hash stored_hash - current_hash=$(shasum -a 256 "$REQ_FILE" | cut -d' ' -f1) + local current_hash="" + local stored_hash="" + + current_hash=$(shasum -a 256 "$REQ_FILE" | cut -d" " -f1) stored_hash=$(cat "$REQ_HASH_FILE" 2>/dev/null || true) if [[ ! -f "$REQ_HASH_FILE" ]] || [[ "$current_hash" != "$stored_hash" ]]; then - echo -e "${CYAN}Updating dependencies...${NC}" + echo -e "${BLUE}Updating dependencies...${NC}" pip install --upgrade pip pip install --no-cache-dir -r "$REQ_FILE" echo "$current_hash" > "$REQ_HASH_FILE" else - echo -e "${GREEN}Dependencies are up to date${NC}" - fi -} - -install_helper() { - if [[ "$COPY_HELPER" == true ]]; then - if [[ -f "$HELPER_SCRIPT" ]]; then - echo -e "${CYAN}Installing helper script...${NC}" - - if [[ ! -w "$INSTALL_DIR" ]]; then - echo -e "${YELLOW}Installing to $INSTALL_DIR requires sudo privileges${NC}" - sudo cp "$HELPER_SCRIPT" "$INSTALL_DIR/" - sudo chmod +x "$INSTALL_DIR/$(basename "$HELPER_SCRIPT")" - else - cp "$HELPER_SCRIPT" "$INSTALL_DIR/" - chmod +x "$INSTALL_DIR/$(basename "$HELPER_SCRIPT")" - fi - - echo -e "${GREEN}Helper script installed to: ${YELLOW}$INSTALL_DIR/$(basename "$HELPER_SCRIPT")${NC}" - else - echo -e "${YELLOW}Warning: Helper script $HELPER_SCRIPT not found, skipping installation${NC}" - fi + echo "Dependencies are up to date" fi } run_application() { - echo -e "${CYAN}Running application in development mode...${NC}" - echo -e "${YELLOW}Source file: $SRC_FILE${NC}" - echo -e "${YELLOW}Virtual environment: $VENV_DIR${NC}" + echo -e "${BLUE}Running application in development mode...${NC}" + echo "Source file: $SRC_FILE" + echo "Virtual environment: $VENV_DIR" echo "" - if ! python3 "$SRC_FILE"; then echo -e "\n${RED}Application exited with error${NC}" >&2 exit 1 fi } -parse_args() { - while [[ $# -gt 0 ]]; do - case $1 in - -c) - COPY_HELPER=true - shift - ;; - *) - echo -e "${RED}Error: Unknown option '$1'${NC}" >&2 - echo -e "${CYAN}Usage: $0 [-c]${NC}" >&2 - echo -e " -c Copy volt-helper script to $INSTALL_DIR" >&2 - exit 1 - ;; - esac - done +remove_venv() { + if [[ -d "$VENV_DIR" ]]; then + echo -e "${BLUE}Removing virtual environment: $VENV_DIR${NC}" + rm -rf "$VENV_DIR" + echo "Virtual environment removed successfully" + else + echo "No virtual environment found at: $VENV_DIR" + fi } main() { - trap EXIT + trap cleanup EXIT - parse_args "$@" + if [[ "${1:-}" == "-r" ]]; then + check_commands + remove_venv + exit 0 + fi + + if [[ $EUID -eq 0 ]]; then + echo -e "${RED}Error: Do not run the application with sudo${NC}" >&2 + echo "Please run without sudo: $0" >&2 + exit 1 + fi check_commands verify_files create_venv - - echo -e "${CYAN}Activating virtual environment...${NC}" + echo -e "${BLUE}Activating virtual environment...${NC}" source "$VENV_DIR/bin/activate" - update_dependencies - install_helper - - echo -e "\n${GREEN}Setup complete! Starting application...${NC}" - echo -e "${CYAN}────────────────────────────────────────${NC}" - + echo -e "\nSetup complete! Starting application..." + echo -e "${BLUE}────────────────────────────────────────${NC}" run_application }