From 06bd94d119fecffc5519ec2e2b6562e456fc263e Mon Sep 17 00:00:00 2001 From: Omni Date: Tue, 28 Oct 2025 21:18:54 +0000 Subject: [PATCH] Sync from development - prepare for v0.1.6.5 --- CHANGELOG.md | 11 +++ jackify/__init__.py | 2 +- jackify/backend/handlers/menu_handler.py | 14 +++- jackify/backend/handlers/modlist_handler.py | 33 ++++++++- jackify/backend/handlers/path_handler.py | 20 +++++- .../backend/handlers/protontricks_handler.py | 24 ++++++- jackify/backend/handlers/wine_utils.py | 12 +++- .../services/automated_prefix_service.py | 13 +++- jackify/backend/services/modlist_service.py | 6 +- .../services/platform_detection_service.py | 67 +++++++++++++++++++ jackify/frontends/gui/main.py | 36 +++++++--- .../gui/screens/configure_existing_modlist.py | 4 +- .../gui/screens/configure_new_modlist.py | 30 +++++---- .../frontends/gui/screens/install_modlist.py | 46 +++++++++---- 14 files changed, 266 insertions(+), 52 deletions(-) create mode 100644 jackify/backend/services/platform_detection_service.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d8e478..72a352e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Jackify Changelog +## v0.1.6.5 - Steam Deck SD Card Path Fix +**Release Date:** October 27, 2025 + +### Bug Fixes +- **Fixed Steam Deck SD card path manipulation** when jackify-engine installed +- **Fixed Ubuntu Qt platform plugin errors** by bundling XCB libraries +- **Added Flatpak GE-Proton detection** and protontricks installation choices +- **Extended Steam Deck SD card timeouts** for slower I/O operations + +--- + ## v0.1.6.4 - Flatpak Steam Detection Hotfix **Release Date:** October 24, 2025 diff --git a/jackify/__init__.py b/jackify/__init__.py index 23eb453..7232548 100644 --- a/jackify/__init__.py +++ b/jackify/__init__.py @@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing Wabbajack modlists natively on Linux systems. """ -__version__ = "0.1.6.4" +__version__ = "0.1.6.5" diff --git a/jackify/backend/handlers/menu_handler.py b/jackify/backend/handlers/menu_handler.py index 5ca21be..ef60133 100644 --- a/jackify/backend/handlers/menu_handler.py +++ b/jackify/backend/handlers/menu_handler.py @@ -152,8 +152,10 @@ class ModlistMenuHandler: self.path_handler = PathHandler() self.vdf_handler = VDFHandler() - # Determine Steam Deck status (already done by ConfigHandler, use it) - self.steamdeck = config_handler.settings.get('steamdeck', False) + # Determine Steam Deck status using centralized detection + from ..services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + self.steamdeck = platform_service.is_steamdeck # Create the resolution handler self.resolution_handler = ResolutionHandler() @@ -178,7 +180,13 @@ class ModlistMenuHandler: self.logger.error(f"Error initializing ModlistMenuHandler: {e}") # Initialize with defaults/empty to prevent errors self.filesystem_handler = FileSystemHandler() - self.steamdeck = False + # Use centralized detection even in fallback + try: + from ..services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + self.steamdeck = platform_service.is_steamdeck + except: + self.steamdeck = False # Final fallback self.modlist_handler = None def show_modlist_menu(self): diff --git a/jackify/backend/handlers/modlist_handler.py b/jackify/backend/handlers/modlist_handler.py index 30a39e8..158c283 100644 --- a/jackify/backend/handlers/modlist_handler.py +++ b/jackify/backend/handlers/modlist_handler.py @@ -109,6 +109,12 @@ class ModlistHandler: self.logger = logging.getLogger(__name__) self.logger.propagate = False self.steamdeck = steamdeck + + # DEBUG: Log ModlistHandler instantiation details for SD card path debugging + import traceback + caller_info = traceback.extract_stack()[-2] # Get caller info + self.logger.debug(f"[SD_CARD_DEBUG] ModlistHandler created: id={id(self)}, steamdeck={steamdeck}") + self.logger.debug(f"[SD_CARD_DEBUG] Created from: {caller_info.filename}:{caller_info.lineno} in {caller_info.name}()") self.steam_path: Optional[Path] = None self.verbose = verbose # Store verbose flag self.mo2_path: Optional[Path] = None @@ -321,13 +327,24 @@ class ModlistHandler: # Determine if modlist is on SD card (Steam Deck only) # On non-Steam Deck systems, /media mounts should use Z: drive, not D: drive - if (str(self.modlist_dir).startswith("/run/media") or str(self.modlist_dir).startswith("/media")) and self.steamdeck: + is_on_sdcard_path = str(self.modlist_dir).startswith("/run/media") or str(self.modlist_dir).startswith("/media") + + # DEBUG: Log SD card detection logic + self.logger.debug(f"[SD_CARD_DEBUG] SD card detection for instance id={id(self)}:") + self.logger.debug(f"[SD_CARD_DEBUG] modlist_dir: {self.modlist_dir}") + self.logger.debug(f"[SD_CARD_DEBUG] is_on_sdcard_path: {is_on_sdcard_path}") + self.logger.debug(f"[SD_CARD_DEBUG] self.steamdeck: {self.steamdeck}") + + if is_on_sdcard_path and self.steamdeck: self.modlist_sdcard = True self.logger.info("Modlist appears to be on an SD card (Steam Deck).") + self.logger.debug(f"[SD_CARD_DEBUG] Set modlist_sdcard=True") else: self.modlist_sdcard = False - if (str(self.modlist_dir).startswith("/run/media") or str(self.modlist_dir).startswith("/media")) and not self.steamdeck: + self.logger.debug(f"[SD_CARD_DEBUG] Set modlist_sdcard=False because: is_on_sdcard_path={is_on_sdcard_path} AND steamdeck={self.steamdeck}") + if is_on_sdcard_path and not self.steamdeck: self.logger.info("Modlist on /media mount detected on non-Steam Deck system - using Z: drive mapping.") + self.logger.debug("[SD_CARD_DEBUG] This is the ROOT CAUSE - SD card path but steamdeck=False!") # Find and set compatdata path now that we have appid # Ensure PathHandler is available (should be initialized in __init__) @@ -812,6 +829,15 @@ class ModlistHandler: # Conditionally update binary and working directory paths # Skip for jackify-engine workflows since paths are already correct # Exception: Always run for SD card installs to fix Z:/run/media/... to D:/... paths + + # DEBUG: Add comprehensive logging to identify Steam Deck SD card path manipulation issues + engine_installed = getattr(self, 'engine_installed', False) + self.logger.debug(f"[SD_CARD_DEBUG] ModlistHandler instance: id={id(self)}") + self.logger.debug(f"[SD_CARD_DEBUG] engine_installed: {engine_installed}") + self.logger.debug(f"[SD_CARD_DEBUG] modlist_sdcard: {self.modlist_sdcard}") + self.logger.debug(f"[SD_CARD_DEBUG] steamdeck parameter passed to constructor: {getattr(self, 'steamdeck', 'NOT_SET')}") + self.logger.debug(f"[SD_CARD_DEBUG] Path manipulation condition: not {engine_installed} or {self.modlist_sdcard} = {not engine_installed or self.modlist_sdcard}") + if not getattr(self, 'engine_installed', False) or self.modlist_sdcard: # Convert steamapps/common path to library root path steam_libraries = None @@ -831,7 +857,8 @@ class ModlistHandler: print("Error: Failed to update binary and working directory paths in ModOrganizer.ini.") return False # Abort on failure else: - self.logger.debug("Skipping path manipulation - jackify-engine already set correct paths in ModOrganizer.ini") + self.logger.debug("[SD_CARD_DEBUG] Skipping path manipulation - jackify-engine already set correct paths in ModOrganizer.ini") + self.logger.debug(f"[SD_CARD_DEBUG] SKIPPED because: engine_installed={engine_installed} and modlist_sdcard={self.modlist_sdcard}") self.logger.info("Step 8: Updating ModOrganizer.ini paths... Done") # Step 9: Update Resolution Settings (if applicable) diff --git a/jackify/backend/handlers/path_handler.py b/jackify/backend/handlers/path_handler.py index 63f461a..16ea53f 100644 --- a/jackify/backend/handlers/path_handler.py +++ b/jackify/backend/handlers/path_handler.py @@ -777,17 +777,35 @@ class PathHandler: # Extract existing gamePath to use as source of truth for vanilla game location existing_game_path = None - for line in lines: + gamepath_line_index = -1 + for i, line in enumerate(lines): if re.match(r'^\s*gamepath\s*=.*@ByteArray\(([^)]+)\)', line, re.IGNORECASE): match = re.search(r'@ByteArray\(([^)]+)\)', line) if match: raw_path = match.group(1) + gamepath_line_index = i # Convert Windows path back to Linux path if raw_path.startswith(('Z:', 'D:')): linux_path = raw_path[2:].replace('\\\\', '/').replace('\\', '/') existing_game_path = linux_path logger.debug(f"Extracted existing gamePath: {existing_game_path}") break + + # Special handling for gamePath in three-true scenario (engine_installed + steamdeck + sdcard) + if modlist_sdcard and existing_game_path and existing_game_path.startswith('/run/media') and gamepath_line_index != -1: + # Simple manual stripping of /run/media/deck/UUID pattern for SD card paths + # Match /run/media/deck/[UUID]/Games/... and extract just /Games/... + sdcard_pattern = r'^/run/media/deck/[^/]+(/Games/.*)$' + match = re.match(sdcard_pattern, existing_game_path) + if match: + stripped_path = match.group(1) # Just the /Games/... part + new_gamepath_value = f"D:\\\\{stripped_path.replace('/', '\\\\')}" + new_gamepath_line = f"gamePath = @ByteArray({new_gamepath_value})\n" + + logger.info(f"Updating gamePath for SD card: {lines[gamepath_line_index].strip()} -> {new_gamepath_line.strip()}") + lines[gamepath_line_index] = new_gamepath_line + else: + logger.warning(f"SD card path doesn't match expected pattern: {existing_game_path}") game_path_updated = False binary_paths_updated = 0 diff --git a/jackify/backend/handlers/protontricks_handler.py b/jackify/backend/handlers/protontricks_handler.py index 038921a..397c292 100644 --- a/jackify/backend/handlers/protontricks_handler.py +++ b/jackify/backend/handlers/protontricks_handler.py @@ -149,9 +149,29 @@ class ProtontricksHandler: should_install = True else: try: - response = input("Protontricks not found. Install the Flatpak version? (Y/n): ").lower() - if response == 'y' or response == '': + print("\nProtontricks not found. Choose installation method:") + print("1. Install via Flatpak (automatic)") + print("2. Install via native package manager (manual)") + print("3. Skip (Use bundled winetricks instead)") + choice = input("Enter choice (1/2/3): ").strip() + + if choice == '1' or choice == '': should_install = True + elif choice == '2': + print("\nTo install protontricks via your system package manager:") + print("• Ubuntu/Debian: sudo apt install protontricks") + print("• Fedora: sudo dnf install protontricks") + print("• Arch Linux: sudo pacman -S protontricks") + print("• openSUSE: sudo zypper install protontricks") + print("\nAfter installation, please rerun Jackify.") + return False + elif choice == '3': + print("Skipping protontricks installation. Will use bundled winetricks for component installation.") + logger.info("User chose to skip protontricks and use winetricks fallback") + return False + else: + print("Invalid choice. Installation cancelled.") + return False except KeyboardInterrupt: print("\nInstallation cancelled.") return False diff --git a/jackify/backend/handlers/wine_utils.py b/jackify/backend/handlers/wine_utils.py index 6632604..92999a7 100644 --- a/jackify/backend/handlers/wine_utils.py +++ b/jackify/backend/handlers/wine_utils.py @@ -668,7 +668,10 @@ class WineUtils: # Add standard compatibility tool locations (covers edge cases like Flatpak) compatibility_paths.extend([ Path.home() / ".steam/root/compatibilitytools.d", - Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/compatibilitytools.d" + Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/compatibilitytools.d", + # Flatpak GE-Proton extension paths + Path.home() / ".var/app/com.valvesoftware.Steam.CompatibilityTool.Proton-GE/.local/share/Steam/compatibilitytools.d", + Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/compatibilitytools.d/GE-Proton" ]) # Special handling for Proton 9: try all possible directory names if proton_version.strip().startswith("Proton 9"): @@ -822,7 +825,12 @@ class WineUtils: """ compat_paths = [ Path.home() / ".steam/steam/compatibilitytools.d", - Path.home() / ".local/share/Steam/compatibilitytools.d" + Path.home() / ".local/share/Steam/compatibilitytools.d", + Path.home() / ".steam/root/compatibilitytools.d", + Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/compatibilitytools.d", + # Flatpak GE-Proton extension paths + Path.home() / ".var/app/com.valvesoftware.Steam.CompatibilityTool.Proton-GE/.local/share/Steam/compatibilitytools.d", + Path.home() / ".var/app/com.valvesoftware.Steam/.local/share/Steam/compatibilitytools.d/GE-Proton" ] # Return only existing paths diff --git a/jackify/backend/services/automated_prefix_service.py b/jackify/backend/services/automated_prefix_service.py index dd29e49..433af84 100644 --- a/jackify/backend/services/automated_prefix_service.py +++ b/jackify/backend/services/automated_prefix_service.py @@ -2697,9 +2697,18 @@ echo Prefix creation complete. # Run proton run wineboot -u to initialize the prefix cmd = [str(proton_path), 'run', 'wineboot', '-u'] logger.info(f"Running: {' '.join(cmd)}") - + + # Adjust timeout for SD card installations on Steam Deck (slower I/O) + from ..services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + is_steamdeck_sdcard = (platform_service.is_steamdeck and + str(proton_path).startswith('/run/media/')) + timeout = 180 if is_steamdeck_sdcard else 60 + if is_steamdeck_sdcard: + logger.info(f"Using extended timeout ({timeout}s) for Steam Deck SD card Proton installation") + # Use jackify-engine's approach: UseShellExecute=false, CreateNoWindow=true equivalent - result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=60, + result = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=timeout, shell=False, creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0)) logger.info(f"Proton exit code: {result.returncode}") diff --git a/jackify/backend/services/modlist_service.py b/jackify/backend/services/modlist_service.py index 6e85192..cf038b5 100644 --- a/jackify/backend/services/modlist_service.py +++ b/jackify/backend/services/modlist_service.py @@ -34,8 +34,10 @@ class ModlistService: """Lazy initialization of modlist handler.""" if self._modlist_handler is None: from ..handlers.modlist_handler import ModlistHandler - # Initialize with proper dependencies - self._modlist_handler = ModlistHandler() + from ..services.platform_detection_service import PlatformDetectionService + # Initialize with proper dependencies and centralized Steam Deck detection + platform_service = PlatformDetectionService.get_instance() + self._modlist_handler = ModlistHandler(steamdeck=platform_service.is_steamdeck) return self._modlist_handler def _get_wabbajack_handler(self): diff --git a/jackify/backend/services/platform_detection_service.py b/jackify/backend/services/platform_detection_service.py new file mode 100644 index 0000000..5a21e46 --- /dev/null +++ b/jackify/backend/services/platform_detection_service.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Platform Detection Service + +Centralizes platform detection logic (Steam Deck, etc.) to be performed once at application startup +and shared across all components. +""" + +import os +import logging + +logger = logging.getLogger(__name__) + + +class PlatformDetectionService: + """ + Service for detecting platform-specific information once at startup + """ + + _instance = None + _is_steamdeck = None + + def __new__(cls): + """Singleton pattern to ensure only one instance""" + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + + def __init__(self): + """Initialize platform detection if not already done""" + if self._is_steamdeck is None: + self._detect_platform() + + def _detect_platform(self): + """Perform platform detection once""" + logger.debug("Performing platform detection...") + + # Steam Deck detection + self._is_steamdeck = False + try: + if os.path.exists('/etc/os-release'): + with open('/etc/os-release', 'r') as f: + content = f.read().lower() + if 'steamdeck' in content: + self._is_steamdeck = True + logger.info("Steam Deck platform detected") + else: + logger.debug("Non-Steam Deck Linux platform detected") + else: + logger.debug("No /etc/os-release found - assuming non-Steam Deck platform") + except Exception as e: + logger.warning(f"Error detecting Steam Deck platform: {e}") + self._is_steamdeck = False + + logger.debug(f"Platform detection complete: is_steamdeck={self._is_steamdeck}") + + @property + def is_steamdeck(self) -> bool: + """Get Steam Deck detection result""" + if self._is_steamdeck is None: + self._detect_platform() + return self._is_steamdeck + + @classmethod + def get_instance(cls): + """Get the singleton instance""" + return cls() \ No newline at end of file diff --git a/jackify/frontends/gui/main.py b/jackify/frontends/gui/main.py index ef98b3a..823a1e6 100644 --- a/jackify/frontends/gui/main.py +++ b/jackify/frontends/gui/main.py @@ -438,19 +438,37 @@ class SettingsDialog(QDialog): advanced_layout.addWidget(resource_group) - # Component Installation Method Section - component_group = QGroupBox("Component Installation") + # Advanced Tool Options Section + component_group = QGroupBox("Advanced Tool Options") component_group.setStyleSheet("QGroupBox { border: 1px solid #555; border-radius: 6px; margin-top: 8px; padding: 8px; background: #23282d; } QGroupBox:title { subcontrol-origin: margin; left: 10px; padding: 0 3px 0 3px; font-weight: bold; color: #fff; }") component_layout = QVBoxLayout() component_group.setLayout(component_layout) - self.use_winetricks_checkbox = QCheckBox("Use winetricks for component installation (faster)") - self.use_winetricks_checkbox.setChecked(self.config_handler.get('use_winetricks_for_components', True)) - self.use_winetricks_checkbox.setToolTip( - "When enabled: Uses winetricks for most components (faster) and protontricks for legacy .NET versions (dotnet40, dotnet472, dotnet48) which are more reliable.\n" - "When disabled: Uses protontricks for all components (legacy behavior, slower but more compatible)." + # Label for the toggle button + method_label = QLabel("Wine Components Installation:") + component_layout.addWidget(method_label) + + # Toggle button for winetricks/protontricks selection + self.component_toggle = QPushButton("Winetricks") + self.component_toggle.setCheckable(True) + use_winetricks = self.config_handler.get('use_winetricks_for_components', True) + self.component_toggle.setChecked(use_winetricks) + + # Function to update button text based on state + def update_button_text(): + if self.component_toggle.isChecked(): + self.component_toggle.setText("Winetricks") + else: + self.component_toggle.setText("Protontricks") + + self.component_toggle.toggled.connect(update_button_text) + update_button_text() # Set initial text + + self.component_toggle.setToolTip( + "Winetricks: Faster, uses bundled tools (Default)\n" + "Protontricks: Legacy mode, slower but system-compatible" ) - component_layout.addWidget(self.use_winetricks_checkbox) + component_layout.addWidget(self.component_toggle) advanced_layout.addWidget(component_group) advanced_layout.addStretch() # Add stretch to push content to top @@ -726,7 +744,7 @@ class SettingsDialog(QDialog): self.config_handler.set("game_proton_version", resolved_game_version) # Save component installation method preference - self.config_handler.set("use_winetricks_for_components", self.use_winetricks_checkbox.isChecked()) + self.config_handler.set("use_winetricks_for_components", self.component_toggle.isChecked()) # Force immediate save and verify save_result = self.config_handler.save_config() diff --git a/jackify/frontends/gui/screens/configure_existing_modlist.py b/jackify/frontends/gui/screens/configure_existing_modlist.py index 6d65659..9db5f90 100644 --- a/jackify/frontends/gui/screens/configure_existing_modlist.py +++ b/jackify/frontends/gui/screens/configure_existing_modlist.py @@ -37,7 +37,9 @@ class ConfigureExistingModlistScreen(QWidget): self.refresh_paths() # --- Detect Steam Deck --- - steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower() + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + steamdeck = platform_service.is_steamdeck self.shortcut_handler = ShortcutHandler(steamdeck=steamdeck) # Initialize services early diff --git a/jackify/frontends/gui/screens/configure_new_modlist.py b/jackify/frontends/gui/screens/configure_new_modlist.py index b58a44d..c9d88f5 100644 --- a/jackify/frontends/gui/screens/configure_new_modlist.py +++ b/jackify/frontends/gui/screens/configure_new_modlist.py @@ -591,7 +591,9 @@ class ConfigureNewModlistScreen(QWidget): return # --- Shortcut creation will be handled by automated workflow --- from jackify.backend.handlers.shortcut_handler import ShortcutHandler - steamdeck = os.path.exists('/etc/os-release') and 'steamdeck' in open('/etc/os-release').read().lower() + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + steamdeck = platform_service.is_steamdeck shortcut_handler = ShortcutHandler(steamdeck=steamdeck) # Still needed for Steam restart # Check if auto-restart is enabled @@ -723,16 +725,10 @@ class ConfigureNewModlistScreen(QWidget): except Exception as e: self.error_occurred.emit(str(e)) - # Detect Steam Deck once - try: - import os - _is_steamdeck = False - if os.path.exists('/etc/os-release'): - with open('/etc/os-release') as f: - if 'steamdeck' in f.read().lower(): - _is_steamdeck = True - except Exception: - _is_steamdeck = False + # Detect Steam Deck once using centralized service + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + _is_steamdeck = platform_service.is_steamdeck # Create and start the thread self.automated_prefix_thread = AutomatedPrefixThread(modlist_name, install_dir, mo2_exe_path, _is_steamdeck) @@ -928,7 +924,10 @@ class ConfigureNewModlistScreen(QWidget): # Steam assigns a NEW AppID during restart, different from the one we initially created self._safe_append_text(f"Re-detecting AppID for shortcut '{modlist_name}' after Steam restart...") from jackify.backend.handlers.shortcut_handler import ShortcutHandler - shortcut_handler = ShortcutHandler(steamdeck=False) + from jackify.backend.services.platform_detection_service import PlatformDetectionService + + platform_service = PlatformDetectionService.get_instance() + shortcut_handler = ShortcutHandler(steamdeck=platform_service.is_steamdeck) current_appid = shortcut_handler.get_appid_for_shortcut(modlist_name, mo2_exe_path) if not current_appid or not current_appid.isdigit(): @@ -952,7 +951,12 @@ class ConfigureNewModlistScreen(QWidget): # Initialize ModlistHandler with correct parameters path_handler = PathHandler() - modlist_handler = ModlistHandler(steamdeck=False, verbose=False) + + # Use centralized Steam Deck detection + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + + modlist_handler = ModlistHandler(steamdeck=platform_service.is_steamdeck, verbose=False) # Set required properties manually after initialization modlist_handler.modlist_dir = install_dir diff --git a/jackify/frontends/gui/screens/install_modlist.py b/jackify/frontends/gui/screens/install_modlist.py index 19fd5ae..6dbea11 100644 --- a/jackify/frontends/gui/screens/install_modlist.py +++ b/jackify/frontends/gui/screens/install_modlist.py @@ -2112,7 +2112,10 @@ class InstallModlistScreen(QWidget): # Steam assigns a NEW AppID during restart, different from the one we initially created self._safe_append_text(f"Re-detecting AppID for shortcut '{modlist_name}' after Steam restart...") from jackify.backend.handlers.shortcut_handler import ShortcutHandler - shortcut_handler = ShortcutHandler(steamdeck=False) + from jackify.backend.services.platform_detection_service import PlatformDetectionService + + platform_service = PlatformDetectionService.get_instance() + shortcut_handler = ShortcutHandler(steamdeck=platform_service.is_steamdeck) current_appid = shortcut_handler.get_appid_for_shortcut(modlist_name, mo2_exe_path) if not current_appid or not current_appid.isdigit(): @@ -2133,7 +2136,12 @@ class InstallModlistScreen(QWidget): # Initialize ModlistHandler with correct parameters path_handler = PathHandler() - modlist_handler = ModlistHandler(steamdeck=False, verbose=False) + + # Use centralized Steam Deck detection + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + + modlist_handler = ModlistHandler(steamdeck=platform_service.is_steamdeck, verbose=False) # Set required properties manually after initialization modlist_handler.modlist_dir = install_dir @@ -2351,15 +2359,21 @@ class InstallModlistScreen(QWidget): self.context = updated_context # Ensure context is always set debug_print(f"Updated context with new AppID: {new_appid}") + # Get Steam Deck detection once and pass to ConfigThread + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + is_steamdeck = platform_service.is_steamdeck + # Create new config thread with updated context class ConfigThread(QThread): progress_update = Signal(str) configuration_complete = Signal(bool, str, str) error_occurred = Signal(str) - - def __init__(self, context): + + def __init__(self, context, is_steamdeck): super().__init__() self.context = context + self.is_steamdeck = is_steamdeck def run(self): try: @@ -2368,8 +2382,8 @@ class InstallModlistScreen(QWidget): from jackify.backend.models.modlist import ModlistContext from pathlib import Path - # Initialize backend service - system_info = SystemInfo(is_steamdeck=False) + # Initialize backend service with passed Steam Deck detection + system_info = SystemInfo(is_steamdeck=self.is_steamdeck) modlist_service = ModlistService(system_info) # Convert context to ModlistContext for service @@ -2416,7 +2430,7 @@ class InstallModlistScreen(QWidget): self.error_occurred.emit(str(e)) # Start configuration thread - self.config_thread = ConfigThread(updated_context) + self.config_thread = ConfigThread(updated_context, is_steamdeck) self.config_thread.progress_update.connect(self.on_configuration_progress) self.config_thread.configuration_complete.connect(self.on_configuration_complete) self.config_thread.error_occurred.connect(self.on_configuration_error) @@ -2477,15 +2491,21 @@ class InstallModlistScreen(QWidget): def _create_config_thread(self, context): """Create a new ConfigThread with proper lifecycle management""" from PySide6.QtCore import QThread, Signal - + + # Get Steam Deck detection once + from jackify.backend.services.platform_detection_service import PlatformDetectionService + platform_service = PlatformDetectionService.get_instance() + is_steamdeck = platform_service.is_steamdeck + class ConfigThread(QThread): progress_update = Signal(str) configuration_complete = Signal(bool, str, str) error_occurred = Signal(str) - - def __init__(self, context, parent=None): + + def __init__(self, context, is_steamdeck, parent=None): super().__init__(parent) self.context = context + self.is_steamdeck = is_steamdeck def run(self): try: @@ -2494,8 +2514,8 @@ class InstallModlistScreen(QWidget): from jackify.backend.models.modlist import ModlistContext from pathlib import Path - # Initialize backend service - system_info = SystemInfo(is_steamdeck=False) + # Initialize backend service with passed Steam Deck detection + system_info = SystemInfo(is_steamdeck=self.is_steamdeck) modlist_service = ModlistService(system_info) # Convert context to ModlistContext for service @@ -2544,7 +2564,7 @@ class InstallModlistScreen(QWidget): self.progress_update.emit(f"DEBUG: {error_details}") self.error_occurred.emit(str(e)) - return ConfigThread(context, parent=self) + return ConfigThread(context, is_steamdeck, parent=self) def handle_validation_failure(self, missing_text): """Handle failed validation with retry logic"""