|
| 1 | +import subprocess |
| 2 | +import os |
| 3 | +import shutil |
| 4 | +import logging |
| 5 | +from config_manager import ConfigManager |
| 6 | + |
| 7 | +class GameManager: |
| 8 | + def __init__(self, proton_manager): |
| 9 | + self.proton_manager = proton_manager |
| 10 | + self.config_manager = ConfigManager() |
| 11 | + |
| 12 | + def add_game(self, game): |
| 13 | + games = self.config_manager.load_games() |
| 14 | + games.append(game) |
| 15 | + self.config_manager.save_games(games) |
| 16 | + logging.info(f"Added game: {game['name']}") |
| 17 | + |
| 18 | + def remove_game(self, name): |
| 19 | + games = self.config_manager.load_games() |
| 20 | + games = [g for g in games if g['name'] != name] |
| 21 | + self.config_manager.save_games(games) |
| 22 | + logging.info(f"Removed game: {name}") |
| 23 | + |
| 24 | + def launch_game(self, game, gamescope=False): |
| 25 | + runner = game['runner'] |
| 26 | + exe = game['exe'] |
| 27 | + app_id = game.get('app_id', '') |
| 28 | + prefix = game.get('prefix', '') |
| 29 | + launch_options = game.get('launch_options', '').split() |
| 30 | + env = os.environ.copy() |
| 31 | + |
| 32 | + # Validate inputs |
| 33 | + if runner != 'Steam' and not os.path.exists(exe): |
| 34 | + raise Exception(f"Executable does not exist: {exe}") |
| 35 | + if runner == 'Steam' and not app_id: |
| 36 | + raise Exception("Steam App ID not set") |
| 37 | + |
| 38 | + # Set up environment for Wine/Proton |
| 39 | + if runner in ['Wine'] or 'Proton' in runner: |
| 40 | + if not prefix: |
| 41 | + raise Exception("Prefix not set for Wine/Proton runner") |
| 42 | + try: |
| 43 | + os.makedirs(prefix, exist_ok=True) |
| 44 | + # Fix ownership if running as root with sudo |
| 45 | + if os.geteuid() == 0 and 'SUDO_UID' in os.environ: |
| 46 | + user_id = os.environ['SUDO_UID'] |
| 47 | + group_id = os.environ['SUDO_GID'] |
| 48 | + subprocess.run(['chown', '-R', f'{user_id}:{group_id}', prefix], check=True) |
| 49 | + protonfixes_dir = os.path.expanduser('~/.config/protonfixes') |
| 50 | + os.makedirs(protonfixes_dir, exist_ok=True) |
| 51 | + subprocess.run(['chown', '-R', f'{user_id}:{group_id}', protonfixes_dir], check=True) |
| 52 | + except Exception as e: |
| 53 | + raise Exception(f"Failed to set up prefix or protonfixes: {e}") |
| 54 | + env['WINEPREFIX'] = prefix |
| 55 | + if game.get('enable_dxvk', False): |
| 56 | + env['WINEDLLOVERRIDES'] = 'd3d11=n,b;dxgi=n,b' |
| 57 | + env['WINEESYNC'] = '1' if game.get('enable_esync', self.config_manager.settings['enable_esync']) else '0' |
| 58 | + env['WINEFSYNC'] = '1' if game.get('enable_fsync', self.config_manager.settings['enable_fsync']) else '0' |
| 59 | + env['DXVK_ASYNC'] = '1' if game.get('enable_dxvk_async', self.config_manager.settings['enable_dxvk_async']) else '0' |
| 60 | + |
| 61 | + # Build command |
| 62 | + cmd = [] |
| 63 | + if gamescope: |
| 64 | + if not shutil.which('gamescope'): |
| 65 | + raise Exception("Gamescope is not installed. Please install it via your package manager (e.g., dnf install gamescope).") |
| 66 | + cmd = ['gamescope'] |
| 67 | + options_to_remove = [] |
| 68 | + if '--adaptive-sync' in launch_options: |
| 69 | + cmd.append('--adaptive-sync') |
| 70 | + options_to_remove.append('--adaptive-sync') |
| 71 | + if '--force-grab-cursor' in launch_options: |
| 72 | + cmd.append('--force-grab-cursor') |
| 73 | + options_to_remove.append('--force-grab-cursor') |
| 74 | + width_idx = next((i for i, opt in enumerate(launch_options) if opt.startswith('--width=')), None) |
| 75 | + if width_idx is not None: |
| 76 | + cmd.append('-W') |
| 77 | + cmd.append(launch_options[width_idx].split('=')[1]) |
| 78 | + options_to_remove.append(launch_options[width_idx]) |
| 79 | + height_idx = next((i for i, opt in enumerate(launch_options) if opt.startswith('--height=')), None) |
| 80 | + if height_idx is not None: |
| 81 | + cmd.append('-H') |
| 82 | + cmd.append(launch_options[height_idx].split('=')[1]) |
| 83 | + options_to_remove.append(launch_options[height_idx]) |
| 84 | + if '--fullscreen' in launch_options: |
| 85 | + cmd.append('-f') |
| 86 | + options_to_remove.append('--fullscreen') |
| 87 | + if '--bigpicture' in launch_options: |
| 88 | + cmd.extend(['-e', '-f']) |
| 89 | + options_to_remove.append('--bigpicture') |
| 90 | + # Remove processed options |
| 91 | + launch_options = [opt for opt in launch_options if opt not in options_to_remove] |
| 92 | + cmd.append('--') |
| 93 | + |
| 94 | + try: |
| 95 | + if runner == 'Native': |
| 96 | + cmd.extend([exe] + launch_options) |
| 97 | + elif runner == 'Wine': |
| 98 | + if not shutil.which('wine'): |
| 99 | + raise Exception("Wine not installed. Please install it (e.g., dnf install wine).") |
| 100 | + cmd.extend(['wine', exe] + launch_options) |
| 101 | + elif runner == 'Flatpak': |
| 102 | + if not shutil.which('flatpak'): |
| 103 | + raise Exception("Flatpak not installed. Please install it (e.g., dnf install flatpak).") |
| 104 | + cmd.extend(['flatpak', 'run', exe] + launch_options) |
| 105 | + elif runner == 'Steam': |
| 106 | + if shutil.which('flatpak') and not shutil.which('steam'): |
| 107 | + cmd.extend(['flatpak', 'run', 'com.valvesoftware.Steam', '-applaunch', app_id] + launch_options) |
| 108 | + elif shutil.which('steam'): |
| 109 | + cmd.extend(['steam', '-applaunch', app_id] + launch_options) |
| 110 | + else: |
| 111 | + raise Exception("Steam or Flatpak not installed. Please install Steam (e.g., flatpak install flathub com.valvesoftware.Steam).") |
| 112 | + else: # Proton |
| 113 | + proton_bin = self.proton_manager.get_proton_path(runner) |
| 114 | + if not os.path.exists(proton_bin): |
| 115 | + raise Exception(f"Proton binary not found for {runner}") |
| 116 | + # Set up Steam environment for Proton |
| 117 | + steam_dir = os.path.expanduser('~/.local/share/Steam') |
| 118 | + os.makedirs(os.path.join(steam_dir, 'steamapps/compatdata'), exist_ok=True) |
| 119 | + env['STEAM_COMPAT_CLIENT_INSTALL_PATH'] = steam_dir |
| 120 | + env['STEAM_COMPAT_DATA_PATH'] = prefix |
| 121 | + env['STEAM_RUNTIME'] = os.path.join(steam_dir, 'ubuntu12_32/steam-runtime') |
| 122 | + ld_library_path = os.path.join(steam_dir, 'ubuntu12_32') + ':' + os.path.join(steam_dir, 'ubuntu12_64') |
| 123 | + env['LD_LIBRARY_PATH'] = ld_library_path + ':' + env.get('LD_LIBRARY_PATH', '') |
| 124 | + cmd.extend([proton_bin, 'waitforexitandrun', exe] + launch_options) |
| 125 | + |
| 126 | + # Execute command |
| 127 | + log_file = os.path.join(self.config_manager.logs_dir, f"{game['name'].replace(' ', '_')}.log") |
| 128 | + with open(log_file, 'w') as f: |
| 129 | + process = subprocess.Popen(cmd, env=env, stdout=f, stderr=f) |
| 130 | + logging.info(f"Launched game: {game['name']} with cmd: {' '.join(cmd)}") |
| 131 | + except Exception as e: |
| 132 | + logging.error(f"Failed to launch {game['name']}: {e}") |
| 133 | + print(f"Error launching {game['name']}: {e}") |
| 134 | + raise |
0 commit comments