import os import time import subprocess import signal import psutil import logging import sys import shutil from typing import Callable, Optional logger = logging.getLogger(__name__) STRATEGY_JACKIFY = "jackify" STRATEGY_SIMPLE = "simple" def _get_restart_strategy() -> str: """Read restart strategy from config with safe fallback.""" try: from jackify.backend.handlers.config_handler import ConfigHandler strategy = ConfigHandler().get("steam_restart_strategy", STRATEGY_JACKIFY) if strategy == "nak_simple": strategy = STRATEGY_SIMPLE if strategy not in (STRATEGY_JACKIFY, STRATEGY_SIMPLE): return STRATEGY_JACKIFY return strategy except Exception as exc: # pragma: no cover - defensive logging only logger.debug(f"Steam restart: Unable to read strategy from config: {exc}") return STRATEGY_JACKIFY def _strategy_label(strategy: str) -> str: if strategy == STRATEGY_SIMPLE: return "Simple restart" return "Jackify hardened restart" def _get_clean_subprocess_env(): """ Create a clean environment for subprocess calls by stripping bundle-specific environment variables (e.g., frozen AppImage remnants) that can interfere with Steam. CRITICAL: Preserves all display/session variables that Steam needs for GUI: - DISPLAY, WAYLAND_DISPLAY, XDG_SESSION_TYPE, DBUS_SESSION_BUS_ADDRESS, XDG_RUNTIME_DIR, XAUTHORITY, etc. Returns: dict: Cleaned environment dictionary with GUI variables preserved """ env = os.environ.copy() bundle_vars_removed = [] # CRITICAL: Preserve display/session variables that Steam GUI needs # These MUST be kept for Steam to open its GUI window gui_vars_to_preserve = [ 'DISPLAY', 'WAYLAND_DISPLAY', 'XDG_SESSION_TYPE', 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'XAUTHORITY', 'XDG_CURRENT_DESKTOP', 'XDG_SESSION_DESKTOP', 'QT_QPA_PLATFORM', 'GDK_BACKEND', 'XDG_DATA_DIRS', 'XDG_CONFIG_DIRS' ] preserved_gui_vars = {} for var in gui_vars_to_preserve: if var in env: preserved_gui_vars[var] = env[var] logger.debug(f"Steam restart: Preserving GUI variable {var}={env[var][:50] if len(str(env[var])) > 50 else env[var]}") # Remove bundle-specific environment variables if env.pop('_MEIPASS', None): bundle_vars_removed.append('_MEIPASS') if env.pop('_MEIPASS2', None): bundle_vars_removed.append('_MEIPASS2') # Clean library path variables that frozen bundles modify (Linux/Unix) if 'LD_LIBRARY_PATH_ORIG' in env: # Restore original LD_LIBRARY_PATH if it was backed up by the bundler env['LD_LIBRARY_PATH'] = env['LD_LIBRARY_PATH_ORIG'] bundle_vars_removed.append('LD_LIBRARY_PATH (restored from _ORIG)') else: # Remove modified LD_LIBRARY_PATH entries if env.pop('LD_LIBRARY_PATH', None): bundle_vars_removed.append('LD_LIBRARY_PATH (removed)') # Clean PATH of bundle-specific entries if 'PATH' in env and hasattr(sys, '_MEIPASS'): path_entries = env['PATH'].split(os.pathsep) original_count = len(path_entries) # Remove any PATH entries that point to the bundle's temp directory cleaned_path = [p for p in path_entries if not p.startswith(sys._MEIPASS)] env['PATH'] = os.pathsep.join(cleaned_path) if len(cleaned_path) < original_count: bundle_vars_removed.append(f'PATH (removed {original_count - len(cleaned_path)} bundle entries)') # Clean macOS library path (if present) 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) bundle_vars_removed.append('DYLD_LIBRARY_PATH (cleaned)') else: env.pop('DYLD_LIBRARY_PATH', None) bundle_vars_removed.append('DYLD_LIBRARY_PATH (removed)') # Ensure GUI variables are still present (they should be, but double-check) for var, value in preserved_gui_vars.items(): if var not in env: env[var] = value logger.warning(f"Steam restart: Restored GUI variable {var} that was accidentally removed") # Log what was cleaned for debugging if bundle_vars_removed: logger.debug(f"Steam restart: Cleaned bundled environment variables: {', '.join(bundle_vars_removed)}") else: logger.debug("Steam restart: No bundled environment variables detected (likely DEV mode)") # Log preserved GUI variables for debugging if preserved_gui_vars: logger.debug(f"Steam restart: Preserved {len(preserved_gui_vars)} GUI environment variables") return env class SteamRestartError(Exception): pass def is_steam_deck() -> bool: """Detect if running on Steam Deck/SteamOS.""" try: if os.path.exists('/etc/os-release'): with open('/etc/os-release', 'r') as f: content = f.read().lower() if 'steamos' in content or 'steam deck' in content: return True if os.path.exists('/sys/devices/virtual/dmi/id/product_name'): with open('/sys/devices/virtual/dmi/id/product_name', 'r') as f: if 'steam deck' in f.read().lower(): return True if os.environ.get('STEAM_RUNTIME') and os.path.exists('/home/deck'): return True except Exception as e: logger.debug(f"Error detecting Steam Deck: {e}") return False def steam_path_indicates_flatpak(steam_path) -> bool: """True if this Steam path is under the Flatpak Steam app dir (user is running Flatpak Steam).""" if steam_path is None: return False path_str = os.fspath(steam_path) return ".var" in path_str and "app" in path_str and "com.valvesoftware.Steam" in path_str def _flatpak_steam_data_path_exists() -> bool: """True if the Flatpak Steam data directory exists (fallback when resolved_path is None, e.g. AppImage).""" try: from pathlib import Path base = Path.home() / ".var" / "app" / "com.valvesoftware.Steam" for rel in ("data/Steam", ".local/share/Steam", "home/.local/share/Steam"): candidate = base / rel if (candidate / "config" / "loginusers.vdf").exists(): return True return False except Exception as e: logger.debug("Flatpak Steam path check failed: %s", e) return False def _get_flatpak_command(): """Resolve flatpak executable (for detection when PATH is minimal, e.g. AppImage).""" exe = shutil.which("flatpak") if exe: return exe for p in ("/usr/bin/flatpak", "/usr/local/bin/flatpak"): if os.path.isfile(p) and os.access(p, os.X_OK): return p return None def is_flatpak_steam() -> bool: """Detect if Steam is installed as a Flatpak. Uses flatpak CLI only (no dir heuristic) so we don't wrongly choose Flatpak when the user has both Flatpak and native Steam.""" try: flatpak_cmd = _get_flatpak_command() if not flatpak_cmd: return False env = _get_clean_subprocess_env() result = subprocess.run( [flatpak_cmd, "list", "--app"], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, timeout=10, env=env, ) if result.returncode == 0: for line in result.stdout.splitlines(): parts = line.split() if parts and parts[0] == "com.valvesoftware.Steam": return True return False except Exception as e: logger.debug(f"Error detecting Flatpak Steam: {e}") return False def _get_steam_executable(env=None): """Resolve steam executable path for native Steam. Prefer PATH, then common locations.""" env = env or os.environ path_env = env.get("PATH", "") exe = shutil.which("steam", path=path_env) if exe: return exe for candidate in ("/usr/games/steam", "/usr/bin/steam"): if os.path.isfile(candidate) and os.access(candidate, os.X_OK): return candidate return "steam" def get_steam_processes() -> list: """Return a list of psutil.Process objects for running Steam processes.""" steam_procs = [] for proc in psutil.process_iter(['pid', 'name', 'exe', 'cmdline']): try: name = proc.info['name'] exe = proc.info['exe'] cmdline = proc.info['cmdline'] if name and 'steam' in name.lower(): steam_procs.append(proc) elif exe and 'steam' in exe.lower(): steam_procs.append(proc) elif cmdline and any('steam' in str(arg).lower() for arg in cmdline): steam_procs.append(proc) except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): continue return steam_procs def wait_for_steam_exit(timeout: int = 60, check_interval: float = 0.5) -> bool: """Wait for all Steam processes to exit using pgrep (matching existing logic).""" start = time.time() env = _get_clean_subprocess_env() while time.time() - start < timeout: try: result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=env) if result.returncode != 0: return True except Exception as e: logger.debug(f"Error checking Steam processes: {e}") time.sleep(check_interval) return False def _start_steam_simple(is_steamdeck_flag=False, is_flatpak_flag=False, env_override=None) -> bool: """ Start Steam using a simplified restart (single command, no env cleanup). Do NOT use start_new_session - Steam needs to inherit the session for display/tray. """ env = env_override if env_override is not None else os.environ.copy() gui_vars = ['DISPLAY', 'WAYLAND_DISPLAY', 'XDG_SESSION_TYPE', 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR'] for var in gui_vars: if var in env: logger.debug(f"Simple restart: {var}={env[var][:50] if len(str(env[var])) > 50 else env[var]}") else: logger.warning(f"Simple restart: {var} is NOT SET - Steam GUI may fail!") try: if is_steamdeck_flag: logger.info("Simple restart: Steam Deck detected, restarting via systemctl.") subprocess.Popen(["systemctl", "--user", "restart", "app-steam@autostart.service"], env=env) elif is_flatpak_flag: logger.info("Simple restart: Flatpak Steam detected, running flatpak command.") flatpak_cmd = _get_flatpak_command() or "flatpak" subprocess.Popen([flatpak_cmd, "run", "com.valvesoftware.Steam"], env=env, stderr=subprocess.DEVNULL) else: logger.info("Simple restart: launching Steam directly (inheriting session for GUI).") subprocess.Popen("steam", shell=True, env=env) time.sleep(5) check_result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=env) if check_result.returncode == 0: logger.info("Simple restart detected running Steam process.") return True logger.warning("Simple restart did not detect Steam process after launch.") return False except FileNotFoundError as exc: logger.error(f"Simple restart command not found: {exc}") return False except Exception as exc: logger.error(f"Simple restart encountered an error: {exc}") return False def start_steam(is_steamdeck_flag=None, is_flatpak_flag=None, env_override=None, strategy: str = STRATEGY_JACKIFY) -> bool: """ Attempt to start Steam using the exact methods from existing working logic. Args: is_steamdeck_flag: Optional pre-detected Steam Deck status is_flatpak_flag: Optional pre-detected Flatpak Steam status env_override: Optional environment dictionary for subprocess calls strategy: Restart strategy identifier """ if strategy == STRATEGY_SIMPLE: return _start_steam_simple( is_steamdeck_flag=is_steamdeck_flag, is_flatpak_flag=is_flatpak_flag, env_override=env_override or os.environ.copy(), ) env = env_override if env_override is not None else _get_clean_subprocess_env() # Use provided flags or detect _is_steam_deck = is_steamdeck_flag if is_steamdeck_flag is not None else is_steam_deck() _is_flatpak = is_flatpak_flag if is_flatpak_flag is not None else is_flatpak_steam() logger.info( "Starting Steam (strategy=%s, steam_deck=%s, flatpak=%s)", strategy, _is_steam_deck, _is_flatpak, ) try: # Try systemd user service (Steam Deck) - HIGHEST PRIORITY if _is_steam_deck: logger.debug("Using systemctl restart for Steam Deck.") subprocess.Popen(["systemctl", "--user", "restart", "app-steam@autostart.service"], env=env) return True # Check if Flatpak Steam (only if not Steam Deck) if _is_flatpak: logger.info("Flatpak Steam detected - trying flatpak run command first") try: flatpak_cmd = _get_flatpak_command() or "flatpak" logger.debug("Executing: %s run com.valvesoftware.Steam", flatpak_cmd) subprocess.Popen([flatpak_cmd, "run", "com.valvesoftware.Steam"], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(7) # Give Flatpak more time to start # For Flatpak Steam, check for the flatpak process, not steamwebhelper check_result = subprocess.run(['pgrep', '-f', 'com.valvesoftware.Steam'], capture_output=True, timeout=10, env=env) if check_result.returncode == 0: logger.info("Flatpak Steam started successfully") return True else: logger.warning("Flatpak Steam not detected after launch - will NOT fall back to prevent conflicts") return False # Flatpak Steam must use flatpak command, don't fall back except Exception as e: logger.error(f"Flatpak Steam start failed: {e}") return False # Flatpak Steam must use flatpak command, don't fall back steam_exe = _get_steam_executable(env) start_methods = [ {"name": "Popen", "cmd": [steam_exe, "-foreground"], "kwargs": {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL, "stdin": subprocess.DEVNULL, "start_new_session": True, "env": env}}, {"name": "setsid", "cmd": ["setsid", steam_exe, "-foreground"], "kwargs": {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL, "stdin": subprocess.DEVNULL, "env": env}}, {"name": "nohup", "cmd": ["nohup", steam_exe, "-foreground"], "kwargs": {"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL, "stdin": subprocess.DEVNULL, "start_new_session": True, "env": env}} ] for method in start_methods: method_name = method["name"] logger.info(f"Attempting to start Steam using method: {method_name}") try: process = subprocess.Popen(method["cmd"], **method["kwargs"]) if process is not None: logger.info(f"Initiated Steam start with {method_name}.") time.sleep(5) # Wait 5 seconds as in existing logic # Use steamwebhelper for detection (actual Steam process, not steam-powerbuttond) check_result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=env) if check_result.returncode == 0: logger.info(f"Steam process detected after using {method_name}. Proceeding to wait phase.") return True else: logger.warning(f"Steam process not detected after initiating with {method_name}. Trying next method.") else: logger.warning(f"Failed to start process with {method_name}. Trying next method.") except FileNotFoundError: logger.error(f"Command not found for method {method_name} (e.g., setsid, nohup). Trying next method.") except Exception as e: logger.error(f"Error starting Steam with {method_name}: {e}. Trying next method.") return False except Exception as e: logger.error(f"Error starting Steam: {e}") return False def _resolve_steam_path_for_restart(): """Return the Steam path we're using (for shortcuts/config). Used to decide Flatpak vs native when CLI detection fails.""" try: from jackify.backend.services.native_steam_service import NativeSteamService svc = NativeSteamService() if svc.find_steam_user() and svc.steam_path: return svc.steam_path except Exception as e: logger.debug("Could not resolve Steam path for restart: %s", e) return None def shutdown_steam(progress_callback: Optional[Callable[[str], None]] = None, system_info=None) -> bool: """ Shut down Steam completely across all distros. Required before modifying VDF files to prevent race conditions. Args: progress_callback: Optional callback for progress updates system_info: Optional SystemInfo object with pre-detected Steam installation types Returns: True if shutdown successful, False otherwise """ shutdown_env = _get_clean_subprocess_env() _is_steam_deck = system_info.is_steamdeck if system_info else is_steam_deck() resolved_path = _resolve_steam_path_for_restart() if resolved_path is not None: _is_flatpak = steam_path_indicates_flatpak(resolved_path) logger.info("Steam path in use: %s -> flatpak=%s", resolved_path, _is_flatpak) else: _is_flatpak = _flatpak_steam_data_path_exists() if _is_flatpak: logger.info("Steam path in use: (flatpak data path detected) -> flatpak=True") else: _is_flatpak = system_info.is_flatpak_steam if system_info else is_flatpak_steam() def report(msg): if progress_callback: progress_callback(msg) else: logger.info(msg) report("Shutting down Steam...") # Steam Deck: Use systemctl for shutdown if _is_steam_deck: try: report("Steam Deck detected - using systemctl shutdown...") subprocess.run(['systemctl', '--user', 'stop', 'app-steam@autostart.service'], timeout=15, check=False, capture_output=True, env=shutdown_env) time.sleep(2) except Exception as e: logger.debug(f"systemctl stop failed on Steam Deck: {e}") # Flatpak Steam: Use flatpak kill command elif _is_flatpak: try: report("Flatpak Steam detected - stopping via flatpak...") flatpak_cmd = _get_flatpak_command() or "flatpak" subprocess.run([flatpak_cmd, "kill", "com.valvesoftware.Steam"], timeout=15, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=shutdown_env) time.sleep(2) except Exception as e: logger.debug(f"flatpak kill failed: {e}") # All systems: Use pkill approach try: pkill_result = subprocess.run(['pkill', 'steam'], timeout=15, check=False, capture_output=True, env=shutdown_env) logger.debug(f"pkill steam result: {pkill_result.returncode}") time.sleep(2) # Check if Steam is still running check_result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=shutdown_env) if check_result.returncode == 0: # Force kill if still running report("Steam still running - force terminating...") force_result = subprocess.run(['pkill', '-9', 'steam'], timeout=15, check=False, capture_output=True, env=shutdown_env) logger.debug(f"pkill -9 steam result: {force_result.returncode}") time.sleep(2) # Final check final_check = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=shutdown_env) if final_check.returncode != 0: logger.info("Steam processes successfully force terminated.") else: logger.warning("Steam processes may still be running after termination attempts.") report("Steam shutdown incomplete") return False else: logger.info("Steam processes successfully terminated.") except Exception as e: logger.warning(f"Error during Steam shutdown: {e}") report("Steam shutdown had issues") return False report("Steam shut down successfully") return True def robust_steam_restart(progress_callback: Optional[Callable[[str], None]] = None, timeout: int = 60, system_info=None) -> bool: """ Robustly restart Steam across all distros. Returns True on success, False on failure. Optionally accepts a progress_callback(message: str) for UI feedback. Uses aggressive pkill approach for maximum reliability. Args: progress_callback: Optional callback for progress updates timeout: Timeout in seconds for restart operation system_info: Optional SystemInfo object with pre-detected Steam installation types """ shutdown_env = _get_clean_subprocess_env() strategy = _get_restart_strategy() start_env = shutdown_env if strategy == STRATEGY_JACKIFY else os.environ.copy() _is_steam_deck = system_info.is_steamdeck if system_info else is_steam_deck() resolved_path = _resolve_steam_path_for_restart() if resolved_path is not None: _is_flatpak = steam_path_indicates_flatpak(resolved_path) logger.info("Steam path in use: %s -> flatpak=%s", resolved_path, _is_flatpak) else: _is_flatpak = _flatpak_steam_data_path_exists() if _is_flatpak: logger.info("Steam path in use: (flatpak data path detected) -> flatpak=True") else: _is_flatpak = system_info.is_flatpak_steam if system_info else is_flatpak_steam() def report(msg): if progress_callback: progress_callback(msg) else: # Only log directly if no callback (callback chain handles logging) logger.info(msg) report("Shutting down Steam...") report(f"Steam restart strategy: {_strategy_label(strategy)}") # Steam Deck: Use systemctl for shutdown (special handling) - HIGHEST PRIORITY if _is_steam_deck: try: report("Steam Deck detected - using systemctl shutdown...") subprocess.run(['systemctl', '--user', 'stop', 'app-steam@autostart.service'], timeout=15, check=False, capture_output=True, env=shutdown_env) time.sleep(2) except Exception as e: logger.debug(f"systemctl stop failed on Steam Deck: {e}") # Flatpak Steam: Use flatpak kill command (only if not Steam Deck) elif _is_flatpak: try: report("Flatpak Steam detected - stopping via flatpak...") flatpak_cmd = _get_flatpak_command() or "flatpak" subprocess.run([flatpak_cmd, "kill", "com.valvesoftware.Steam"], timeout=15, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, env=shutdown_env) time.sleep(2) except Exception as e: logger.debug(f"flatpak kill failed: {e}") # All systems: Use pkill approach (proven 15/16 test success rate) try: # Skip unreliable steam -shutdown, go straight to pkill pkill_result = subprocess.run(['pkill', 'steam'], timeout=15, check=False, capture_output=True, env=shutdown_env) logger.debug(f"pkill steam result: {pkill_result.returncode}") time.sleep(2) # Check if Steam is still running check_result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=shutdown_env) if check_result.returncode == 0: # Force kill if still running report("Steam still running - force terminating...") force_result = subprocess.run(['pkill', '-9', 'steam'], timeout=15, check=False, capture_output=True, env=shutdown_env) logger.debug(f"pkill -9 steam result: {force_result.returncode}") time.sleep(2) # Final check final_check = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=shutdown_env) if final_check.returncode != 0: logger.info("Steam processes successfully force terminated.") else: # Steam might still be running, but proceed anyway - wait phase will verify logger.warning("Steam processes may still be running after termination attempts. Proceeding to start phase...") report("Steam shutdown incomplete, but proceeding...") else: logger.info("Steam processes successfully terminated.") except Exception as e: # Don't fail completely on shutdown errors - proceed to start phase logger.warning(f"Error during Steam shutdown: {e}. Proceeding to start phase anyway...") report("Steam shutdown had issues, but proceeding...") report("Steam closed successfully.") # Start Steam using platform-specific logic report("Starting Steam...") # Steam Deck: Use systemctl restart (keep existing working approach) if _is_steam_deck: try: subprocess.Popen(["systemctl", "--user", "restart", "app-steam@autostart.service"], env=start_env) logger.info("Steam Deck: Initiated systemctl restart") except Exception as e: logger.error(f"Steam Deck systemctl restart failed: {e}") report("Failed to restart Steam on Steam Deck.") return False else: # All other distros: Use start_steam() which now uses -foreground to ensure GUI opens steam_started = start_steam( is_steamdeck_flag=_is_steam_deck, is_flatpak_flag=_is_flatpak, env_override=start_env, strategy=strategy, ) # Even if start_steam() returns False, Steam might still be starting # Give it a chance by proceeding to wait phase if not steam_started: logger.warning("start_steam() returned False, but proceeding to wait phase in case Steam is starting anyway") report("Steam start command issued, waiting for process...") # Wait for Steam to fully initialize # CRITICAL: Use steamwebhelper (actual Steam process), not "steam" (matches steam-powerbuttond, etc.) report("Waiting for Steam to fully start") logger.info("Waiting up to 3 minutes (180 seconds) for Steam to fully initialize...") max_startup_wait = 180 # Increased from 120 to 180 seconds (3 minutes) for slower systems elapsed_wait = 0 initial_wait_done = False last_status_log = 0 # Track when we last logged status while elapsed_wait < max_startup_wait: try: # Log status every 30 seconds so user knows we're still waiting if elapsed_wait - last_status_log >= 30: remaining = max_startup_wait - elapsed_wait logger.info(f"Still waiting for Steam... ({elapsed_wait}s elapsed, {remaining}s remaining)") if progress_callback: progress_callback(f"Waiting for Steam... ({elapsed_wait}s / {max_startup_wait}s)") last_status_log = elapsed_wait # Use steamwebhelper for detection (matches shutdown logic) result = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env) if result.returncode == 0: if not initial_wait_done: logger.info(f"Steam process detected at {elapsed_wait}s. Waiting additional time for full initialization...") initial_wait_done = True time.sleep(5) elapsed_wait += 5 # Require at least 20 seconds of stable detection (increased from 15) if initial_wait_done and elapsed_wait >= 20: final_check = subprocess.run(['pgrep', '-f', 'steamwebhelper'], capture_output=True, timeout=10, env=start_env) if final_check.returncode == 0: report("Steam started successfully.") logger.info(f"Steam confirmed running after {elapsed_wait}s wait.") return True else: logger.warning("Steam process disappeared during final initialization wait, continuing to wait...") # Don't break - continue waiting in case Steam is still starting initial_wait_done = False # Reset to allow re-detection else: logger.debug(f"Steam process not yet detected. Waiting... ({elapsed_wait + 5}s)") time.sleep(5) elapsed_wait += 5 except Exception as e: logger.warning(f"Error during Steam startup wait: {e}") time.sleep(5) elapsed_wait += 5 # Only reach here if we've waited the full duration report(f"Steam did not start within {max_startup_wait}s timeout.") logger.error(f"Steam failed to start/initialize within the allowed time ({elapsed_wait}s elapsed).") return False