mirror of
https://github.com/Omni-guides/Jackify.git
synced 2026-06-17 12:47:44 +02:00
Sync from development - prepare for v0.3.0
This commit is contained in:
195
jackify/backend/handlers/protontricks_detection.py
Normal file
195
jackify/backend/handlers/protontricks_detection.py
Normal file
@@ -0,0 +1,195 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Protontricks detection and version mixin.
|
||||
Extracted from protontricks_handler for file-size and domain separation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
import sys
|
||||
|
||||
from .subprocess_utils import get_clean_subprocess_env
|
||||
|
||||
|
||||
class ProtontricksDetectionMixin:
|
||||
"""Mixin providing protontricks detection, Steam dir, bundled paths, and version checks."""
|
||||
|
||||
def _get_steam_dir_from_libraryfolders(self) -> Optional[Path]:
|
||||
"""Determine Steam installation directory from libraryfolders.vdf."""
|
||||
from ..handlers.path_handler import PathHandler
|
||||
vdf_paths = [
|
||||
Path.home() / ".steam/steam/config/libraryfolders.vdf",
|
||||
Path.home() / ".local/share/Steam/config/libraryfolders.vdf",
|
||||
Path.home() / ".steam/root/config/libraryfolders.vdf",
|
||||
Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/config/libraryfolders.vdf",
|
||||
Path.home() / ".var/app/com.valvesoftware.Steam/data/Steam/config/libraryfolders.vdf",
|
||||
]
|
||||
for vdf_path in vdf_paths:
|
||||
if vdf_path.is_file():
|
||||
steam_dir = vdf_path.parent.parent
|
||||
if (steam_dir / "steamapps").exists():
|
||||
self.logger.debug(f"Determined STEAM_DIR from libraryfolders.vdf: {steam_dir}")
|
||||
return steam_dir
|
||||
library_paths = PathHandler.get_all_steam_library_paths()
|
||||
if library_paths:
|
||||
first_lib = library_paths[0]
|
||||
if '.var/app/com.valvesoftware.Steam' in str(first_lib):
|
||||
data_steam = Path.home() / ".var/app/com.valvesoftware.Steam/data/Steam"
|
||||
if (data_steam / "steamapps").exists():
|
||||
self.logger.debug(f"Determined STEAM_DIR from Flatpak data path: {data_steam}")
|
||||
return data_steam
|
||||
if (first_lib / "steamapps").exists():
|
||||
self.logger.debug(f"Determined STEAM_DIR from Flatpak library path: {first_lib}")
|
||||
return first_lib
|
||||
elif (first_lib / "steamapps").exists():
|
||||
self.logger.debug(f"Determined STEAM_DIR from native library path: {first_lib}")
|
||||
return first_lib
|
||||
self.logger.warning("Could not determine STEAM_DIR from libraryfolders.vdf")
|
||||
return None
|
||||
|
||||
def _get_bundled_winetricks_path(self) -> Optional[Path]:
|
||||
"""Get path to bundled winetricks (AppImage and dev)."""
|
||||
possible_paths = []
|
||||
if os.environ.get('APPDIR'):
|
||||
possible_paths.append(Path(os.environ['APPDIR']) / 'opt' / 'jackify' / 'tools' / 'winetricks')
|
||||
module_dir = Path(__file__).parent.parent.parent
|
||||
possible_paths.append(module_dir / 'tools' / 'winetricks')
|
||||
for path in possible_paths:
|
||||
if path.exists() and os.access(path, os.X_OK):
|
||||
self.logger.debug(f"Found bundled winetricks at: {path}")
|
||||
return path
|
||||
self.logger.warning(f"Bundled winetricks not found. Tried paths: {possible_paths}")
|
||||
return None
|
||||
|
||||
def _get_bundled_cabextract_path(self) -> Optional[Path]:
|
||||
"""Get path to bundled cabextract (AppImage and dev)."""
|
||||
possible_paths = []
|
||||
if os.environ.get('APPDIR'):
|
||||
possible_paths.append(Path(os.environ['APPDIR']) / 'opt' / 'jackify' / 'tools' / 'cabextract')
|
||||
module_dir = Path(__file__).parent.parent.parent
|
||||
possible_paths.append(module_dir / 'tools' / 'cabextract')
|
||||
for path in possible_paths:
|
||||
if path.exists() and os.access(path, os.X_OK):
|
||||
self.logger.debug(f"Found bundled cabextract at: {path}")
|
||||
return path
|
||||
self.logger.warning(f"Bundled cabextract not found. Tried paths: {possible_paths}")
|
||||
return None
|
||||
|
||||
def _get_bundled_protontricks_wrapper_path(self) -> Optional[str]:
|
||||
"""Return path to bundled protontricks wrapper script if any. Returns None to use python -m fallback."""
|
||||
return None
|
||||
|
||||
def _get_clean_subprocess_env(self):
|
||||
"""Create clean environment for subprocess (remove AppImage/bundle vars)."""
|
||||
env = get_clean_subprocess_env()
|
||||
if 'LD_LIBRARY_PATH_ORIG' in env:
|
||||
env['LD_LIBRARY_PATH'] = env['LD_LIBRARY_PATH_ORIG']
|
||||
else:
|
||||
env.pop('LD_LIBRARY_PATH', None)
|
||||
if 'DYLD_LIBRARY_PATH' in env and hasattr(sys, '_MEIPASS'):
|
||||
dyld_entries = env['DYLD_LIBRARY_PATH'].split(os.pathsep)
|
||||
cleaned_dyld = [p for p in dyld_entries if not p.startswith(sys._MEIPASS)]
|
||||
if cleaned_dyld:
|
||||
env['DYLD_LIBRARY_PATH'] = os.pathsep.join(cleaned_dyld)
|
||||
else:
|
||||
env.pop('DYLD_LIBRARY_PATH', None)
|
||||
return env
|
||||
|
||||
def _get_native_steam_service(self):
|
||||
"""Get native Steam operations service instance."""
|
||||
if self._native_steam_service is None:
|
||||
from ..services.native_steam_operations_service import NativeSteamOperationsService
|
||||
self._native_steam_service = NativeSteamOperationsService(steamdeck=self.steamdeck)
|
||||
return self._native_steam_service
|
||||
|
||||
def detect_protontricks(self):
|
||||
"""Detect if protontricks is installed (native or flatpak). Returns True if found."""
|
||||
self.logger.debug("Detecting if protontricks is installed...")
|
||||
protontricks_path_which = shutil.which("protontricks")
|
||||
self.flatpak_path = shutil.which("flatpak")
|
||||
if protontricks_path_which:
|
||||
try:
|
||||
with open(protontricks_path_which, 'r') as f:
|
||||
content = f.read()
|
||||
if "flatpak run" in content:
|
||||
self.logger.debug(f"Detected Protontricks is a Flatpak wrapper at {protontricks_path_which}")
|
||||
self.which_protontricks = 'flatpak'
|
||||
else:
|
||||
self.logger.info(f"Native Protontricks found at {protontricks_path_which}")
|
||||
self.which_protontricks = 'native'
|
||||
self.protontricks_path = protontricks_path_which
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error reading protontricks executable: {e}")
|
||||
try:
|
||||
env = self._get_clean_subprocess_env()
|
||||
result_user = subprocess.run(
|
||||
["flatpak", "list", "--user"],
|
||||
capture_output=True, text=True, env=env
|
||||
)
|
||||
if result_user.returncode == 0 and "com.github.Matoking.protontricks" in result_user.stdout:
|
||||
self.logger.info("Flatpak Protontricks is installed (user-level)")
|
||||
self.which_protontricks = 'flatpak'
|
||||
self.flatpak_install_type = 'user'
|
||||
return True
|
||||
result_system = subprocess.run(
|
||||
["flatpak", "list", "--system"],
|
||||
capture_output=True, text=True, env=env
|
||||
)
|
||||
if result_system.returncode == 0 and "com.github.Matoking.protontricks" in result_system.stdout:
|
||||
self.logger.info("Flatpak Protontricks is installed (system-level)")
|
||||
self.which_protontricks = 'flatpak'
|
||||
self.flatpak_install_type = 'system'
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
self.logger.warning("'flatpak' command not found. Cannot check for Flatpak Protontricks.")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Unexpected error checking flatpak: {e}")
|
||||
self.logger.warning("Protontricks not found (native or flatpak).")
|
||||
return False
|
||||
|
||||
def _get_flatpak_run_args(self) -> List[str]:
|
||||
"""Get flatpak run arguments (--user or --system)."""
|
||||
base_args = ["flatpak", "run"]
|
||||
if self.flatpak_install_type == 'user':
|
||||
base_args.append("--user")
|
||||
elif self.flatpak_install_type == 'system':
|
||||
base_args.append("--system")
|
||||
return base_args
|
||||
|
||||
def _get_flatpak_alias_string(self, command=None) -> str:
|
||||
"""Get flatpak alias string for bashrc."""
|
||||
flag = f"--{self.flatpak_install_type}" if self.flatpak_install_type else ""
|
||||
if command:
|
||||
return f"flatpak run {flag} --command={command} com.github.Matoking.protontricks" if flag else f"flatpak run --command={command} com.github.Matoking.protontricks"
|
||||
return f"flatpak run {flag} com.github.Matoking.protontricks" if flag else "flatpak run com.github.Matoking.protontricks"
|
||||
|
||||
def check_protontricks_version(self):
|
||||
"""Check if protontricks version is sufficient (>= 1.12). Returns True if OK."""
|
||||
try:
|
||||
if self.which_protontricks == 'flatpak':
|
||||
cmd = self._get_flatpak_run_args() + ["com.github.Matoking.protontricks", "-V"]
|
||||
else:
|
||||
cmd = ["protontricks", "-V"]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
version_str = result.stdout.split(' ')[1].strip('()')
|
||||
cleaned_version = re.sub(r'[^0-9.]', '', version_str)
|
||||
self.protontricks_version = cleaned_version
|
||||
version_parts = cleaned_version.split('.')
|
||||
if len(version_parts) >= 2:
|
||||
major, minor = int(version_parts[0]), int(version_parts[1])
|
||||
if major < 1 or (major == 1 and minor < 12):
|
||||
self.logger.error(f"Protontricks version {cleaned_version} is too old. Version 1.12.0 or newer is required.")
|
||||
return False
|
||||
return True
|
||||
self.logger.error(f"Could not parse protontricks version: {cleaned_version}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error checking protontricks version: {e}")
|
||||
return False
|
||||
Reference in New Issue
Block a user