Sync from development - prepare for v0.1.6.5

This commit is contained in:
Omni
2025-10-28 21:18:54 +00:00
parent 52806f4116
commit 06bd94d119
14 changed files with 266 additions and 52 deletions

View File

@@ -1,5 +1,16 @@
# Jackify Changelog # 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 ## v0.1.6.4 - Flatpak Steam Detection Hotfix
**Release Date:** October 24, 2025 **Release Date:** October 24, 2025

View File

@@ -5,4 +5,4 @@ This package provides both CLI and GUI interfaces for managing
Wabbajack modlists natively on Linux systems. Wabbajack modlists natively on Linux systems.
""" """
__version__ = "0.1.6.4" __version__ = "0.1.6.5"

View File

@@ -152,8 +152,10 @@ class ModlistMenuHandler:
self.path_handler = PathHandler() self.path_handler = PathHandler()
self.vdf_handler = VDFHandler() self.vdf_handler = VDFHandler()
# Determine Steam Deck status (already done by ConfigHandler, use it) # Determine Steam Deck status using centralized detection
self.steamdeck = config_handler.settings.get('steamdeck', False) from ..services.platform_detection_service import PlatformDetectionService
platform_service = PlatformDetectionService.get_instance()
self.steamdeck = platform_service.is_steamdeck
# Create the resolution handler # Create the resolution handler
self.resolution_handler = ResolutionHandler() self.resolution_handler = ResolutionHandler()
@@ -178,7 +180,13 @@ class ModlistMenuHandler:
self.logger.error(f"Error initializing ModlistMenuHandler: {e}") self.logger.error(f"Error initializing ModlistMenuHandler: {e}")
# Initialize with defaults/empty to prevent errors # Initialize with defaults/empty to prevent errors
self.filesystem_handler = FileSystemHandler() 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 self.modlist_handler = None
def show_modlist_menu(self): def show_modlist_menu(self):

View File

@@ -109,6 +109,12 @@ class ModlistHandler:
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.logger.propagate = False self.logger.propagate = False
self.steamdeck = steamdeck 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.steam_path: Optional[Path] = None
self.verbose = verbose # Store verbose flag self.verbose = verbose # Store verbose flag
self.mo2_path: Optional[Path] = None self.mo2_path: Optional[Path] = None
@@ -321,13 +327,24 @@ class ModlistHandler:
# Determine if modlist is on SD card (Steam Deck only) # Determine if modlist is on SD card (Steam Deck only)
# On non-Steam Deck systems, /media mounts should use Z: drive, not D: drive # 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.modlist_sdcard = True
self.logger.info("Modlist appears to be on an SD card (Steam Deck).") 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: else:
self.modlist_sdcard = False 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.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 # Find and set compatdata path now that we have appid
# Ensure PathHandler is available (should be initialized in __init__) # Ensure PathHandler is available (should be initialized in __init__)
@@ -812,6 +829,15 @@ class ModlistHandler:
# Conditionally update binary and working directory paths # Conditionally update binary and working directory paths
# Skip for jackify-engine workflows since paths are already correct # 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 # 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: if not getattr(self, 'engine_installed', False) or self.modlist_sdcard:
# Convert steamapps/common path to library root path # Convert steamapps/common path to library root path
steam_libraries = None steam_libraries = None
@@ -831,7 +857,8 @@ class ModlistHandler:
print("Error: Failed to update binary and working directory paths in ModOrganizer.ini.") print("Error: Failed to update binary and working directory paths in ModOrganizer.ini.")
return False # Abort on failure return False # Abort on failure
else: 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") self.logger.info("Step 8: Updating ModOrganizer.ini paths... Done")
# Step 9: Update Resolution Settings (if applicable) # Step 9: Update Resolution Settings (if applicable)

View File

@@ -777,17 +777,35 @@ class PathHandler:
# Extract existing gamePath to use as source of truth for vanilla game location # Extract existing gamePath to use as source of truth for vanilla game location
existing_game_path = None 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): if re.match(r'^\s*gamepath\s*=.*@ByteArray\(([^)]+)\)', line, re.IGNORECASE):
match = re.search(r'@ByteArray\(([^)]+)\)', line) match = re.search(r'@ByteArray\(([^)]+)\)', line)
if match: if match:
raw_path = match.group(1) raw_path = match.group(1)
gamepath_line_index = i
# Convert Windows path back to Linux path # Convert Windows path back to Linux path
if raw_path.startswith(('Z:', 'D:')): if raw_path.startswith(('Z:', 'D:')):
linux_path = raw_path[2:].replace('\\\\', '/').replace('\\', '/') linux_path = raw_path[2:].replace('\\\\', '/').replace('\\', '/')
existing_game_path = linux_path existing_game_path = linux_path
logger.debug(f"Extracted existing gamePath: {existing_game_path}") logger.debug(f"Extracted existing gamePath: {existing_game_path}")
break 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 game_path_updated = False
binary_paths_updated = 0 binary_paths_updated = 0

View File

@@ -149,9 +149,29 @@ class ProtontricksHandler:
should_install = True should_install = True
else: else:
try: try:
response = input("Protontricks not found. Install the Flatpak version? (Y/n): ").lower() print("\nProtontricks not found. Choose installation method:")
if response == 'y' or response == '': 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 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: except KeyboardInterrupt:
print("\nInstallation cancelled.") print("\nInstallation cancelled.")
return False return False

View File

@@ -668,7 +668,10 @@ class WineUtils:
# Add standard compatibility tool locations (covers edge cases like Flatpak) # Add standard compatibility tool locations (covers edge cases like Flatpak)
compatibility_paths.extend([ compatibility_paths.extend([
Path.home() / ".steam/root/compatibilitytools.d", 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 # Special handling for Proton 9: try all possible directory names
if proton_version.strip().startswith("Proton 9"): if proton_version.strip().startswith("Proton 9"):
@@ -822,7 +825,12 @@ class WineUtils:
""" """
compat_paths = [ compat_paths = [
Path.home() / ".steam/steam/compatibilitytools.d", 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 # Return only existing paths

View File

@@ -2697,9 +2697,18 @@ echo Prefix creation complete.
# Run proton run wineboot -u to initialize the prefix # Run proton run wineboot -u to initialize the prefix
cmd = [str(proton_path), 'run', 'wineboot', '-u'] cmd = [str(proton_path), 'run', 'wineboot', '-u']
logger.info(f"Running: {' '.join(cmd)}") 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 # 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)) shell=False, creationflags=getattr(subprocess, 'CREATE_NO_WINDOW', 0))
logger.info(f"Proton exit code: {result.returncode}") logger.info(f"Proton exit code: {result.returncode}")

View File

@@ -34,8 +34,10 @@ class ModlistService:
"""Lazy initialization of modlist handler.""" """Lazy initialization of modlist handler."""
if self._modlist_handler is None: if self._modlist_handler is None:
from ..handlers.modlist_handler import ModlistHandler from ..handlers.modlist_handler import ModlistHandler
# Initialize with proper dependencies from ..services.platform_detection_service import PlatformDetectionService
self._modlist_handler = ModlistHandler() # 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 return self._modlist_handler
def _get_wabbajack_handler(self): def _get_wabbajack_handler(self):

View File

@@ -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()

View File

@@ -438,19 +438,37 @@ class SettingsDialog(QDialog):
advanced_layout.addWidget(resource_group) advanced_layout.addWidget(resource_group)
# Component Installation Method Section # Advanced Tool Options Section
component_group = QGroupBox("Component Installation") 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_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_layout = QVBoxLayout()
component_group.setLayout(component_layout) component_group.setLayout(component_layout)
self.use_winetricks_checkbox = QCheckBox("Use winetricks for component installation (faster)") # Label for the toggle button
self.use_winetricks_checkbox.setChecked(self.config_handler.get('use_winetricks_for_components', True)) method_label = QLabel("Wine Components Installation:")
self.use_winetricks_checkbox.setToolTip( component_layout.addWidget(method_label)
"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)." # 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.addWidget(component_group)
advanced_layout.addStretch() # Add stretch to push content to top 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) self.config_handler.set("game_proton_version", resolved_game_version)
# Save component installation method preference # 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 # Force immediate save and verify
save_result = self.config_handler.save_config() save_result = self.config_handler.save_config()

View File

@@ -37,7 +37,9 @@ class ConfigureExistingModlistScreen(QWidget):
self.refresh_paths() self.refresh_paths()
# --- Detect Steam Deck --- # --- 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) self.shortcut_handler = ShortcutHandler(steamdeck=steamdeck)
# Initialize services early # Initialize services early

View File

@@ -591,7 +591,9 @@ class ConfigureNewModlistScreen(QWidget):
return return
# --- Shortcut creation will be handled by automated workflow --- # --- Shortcut creation will be handled by automated workflow ---
from jackify.backend.handlers.shortcut_handler import ShortcutHandler 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 shortcut_handler = ShortcutHandler(steamdeck=steamdeck) # Still needed for Steam restart
# Check if auto-restart is enabled # Check if auto-restart is enabled
@@ -723,16 +725,10 @@ class ConfigureNewModlistScreen(QWidget):
except Exception as e: except Exception as e:
self.error_occurred.emit(str(e)) self.error_occurred.emit(str(e))
# Detect Steam Deck once # Detect Steam Deck once using centralized service
try: from jackify.backend.services.platform_detection_service import PlatformDetectionService
import os platform_service = PlatformDetectionService.get_instance()
_is_steamdeck = False _is_steamdeck = platform_service.is_steamdeck
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
# Create and start the thread # Create and start the thread
self.automated_prefix_thread = AutomatedPrefixThread(modlist_name, install_dir, mo2_exe_path, _is_steamdeck) 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 # 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...") self._safe_append_text(f"Re-detecting AppID for shortcut '{modlist_name}' after Steam restart...")
from jackify.backend.handlers.shortcut_handler import ShortcutHandler 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) current_appid = shortcut_handler.get_appid_for_shortcut(modlist_name, mo2_exe_path)
if not current_appid or not current_appid.isdigit(): if not current_appid or not current_appid.isdigit():
@@ -952,7 +951,12 @@ class ConfigureNewModlistScreen(QWidget):
# Initialize ModlistHandler with correct parameters # Initialize ModlistHandler with correct parameters
path_handler = PathHandler() 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 # Set required properties manually after initialization
modlist_handler.modlist_dir = install_dir modlist_handler.modlist_dir = install_dir

View File

@@ -2112,7 +2112,10 @@ class InstallModlistScreen(QWidget):
# Steam assigns a NEW AppID during restart, different from the one we initially created # 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...") self._safe_append_text(f"Re-detecting AppID for shortcut '{modlist_name}' after Steam restart...")
from jackify.backend.handlers.shortcut_handler import ShortcutHandler 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) current_appid = shortcut_handler.get_appid_for_shortcut(modlist_name, mo2_exe_path)
if not current_appid or not current_appid.isdigit(): if not current_appid or not current_appid.isdigit():
@@ -2133,7 +2136,12 @@ class InstallModlistScreen(QWidget):
# Initialize ModlistHandler with correct parameters # Initialize ModlistHandler with correct parameters
path_handler = PathHandler() 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 # Set required properties manually after initialization
modlist_handler.modlist_dir = install_dir modlist_handler.modlist_dir = install_dir
@@ -2351,15 +2359,21 @@ class InstallModlistScreen(QWidget):
self.context = updated_context # Ensure context is always set self.context = updated_context # Ensure context is always set
debug_print(f"Updated context with new AppID: {new_appid}") 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 # Create new config thread with updated context
class ConfigThread(QThread): class ConfigThread(QThread):
progress_update = Signal(str) progress_update = Signal(str)
configuration_complete = Signal(bool, str, str) configuration_complete = Signal(bool, str, str)
error_occurred = Signal(str) error_occurred = Signal(str)
def __init__(self, context): def __init__(self, context, is_steamdeck):
super().__init__() super().__init__()
self.context = context self.context = context
self.is_steamdeck = is_steamdeck
def run(self): def run(self):
try: try:
@@ -2368,8 +2382,8 @@ class InstallModlistScreen(QWidget):
from jackify.backend.models.modlist import ModlistContext from jackify.backend.models.modlist import ModlistContext
from pathlib import Path from pathlib import Path
# Initialize backend service # Initialize backend service with passed Steam Deck detection
system_info = SystemInfo(is_steamdeck=False) system_info = SystemInfo(is_steamdeck=self.is_steamdeck)
modlist_service = ModlistService(system_info) modlist_service = ModlistService(system_info)
# Convert context to ModlistContext for service # Convert context to ModlistContext for service
@@ -2416,7 +2430,7 @@ class InstallModlistScreen(QWidget):
self.error_occurred.emit(str(e)) self.error_occurred.emit(str(e))
# Start configuration thread # 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.progress_update.connect(self.on_configuration_progress)
self.config_thread.configuration_complete.connect(self.on_configuration_complete) self.config_thread.configuration_complete.connect(self.on_configuration_complete)
self.config_thread.error_occurred.connect(self.on_configuration_error) self.config_thread.error_occurred.connect(self.on_configuration_error)
@@ -2477,15 +2491,21 @@ class InstallModlistScreen(QWidget):
def _create_config_thread(self, context): def _create_config_thread(self, context):
"""Create a new ConfigThread with proper lifecycle management""" """Create a new ConfigThread with proper lifecycle management"""
from PySide6.QtCore import QThread, Signal 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): class ConfigThread(QThread):
progress_update = Signal(str) progress_update = Signal(str)
configuration_complete = Signal(bool, str, str) configuration_complete = Signal(bool, str, str)
error_occurred = Signal(str) error_occurred = Signal(str)
def __init__(self, context, parent=None): def __init__(self, context, is_steamdeck, parent=None):
super().__init__(parent) super().__init__(parent)
self.context = context self.context = context
self.is_steamdeck = is_steamdeck
def run(self): def run(self):
try: try:
@@ -2494,8 +2514,8 @@ class InstallModlistScreen(QWidget):
from jackify.backend.models.modlist import ModlistContext from jackify.backend.models.modlist import ModlistContext
from pathlib import Path from pathlib import Path
# Initialize backend service # Initialize backend service with passed Steam Deck detection
system_info = SystemInfo(is_steamdeck=False) system_info = SystemInfo(is_steamdeck=self.is_steamdeck)
modlist_service = ModlistService(system_info) modlist_service = ModlistService(system_info)
# Convert context to ModlistContext for service # Convert context to ModlistContext for service
@@ -2544,7 +2564,7 @@ class InstallModlistScreen(QWidget):
self.progress_update.emit(f"DEBUG: {error_details}") self.progress_update.emit(f"DEBUG: {error_details}")
self.error_occurred.emit(str(e)) 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): def handle_validation_failure(self, missing_text):
"""Handle failed validation with retry logic""" """Handle failed validation with retry logic"""