Rework EGS integration #1
| @@ -15,7 +15,7 @@ | ||||
| - [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор) | ||||
| - [ ] Продумать систему вкладок вместо текущей | ||||
| - [ ] [Добавить сессию Gamescope, аналогичную той, что используется в SteamOS](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt) | ||||
| - [ ] Разобраться почему теряется часть стилей в Gamescope | ||||
| - [X] Разобраться почему теряется часть стилей в Gamescope | ||||
| - [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800) | ||||
| - [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots) | ||||
| - [X] Получать описания и названия игр из базы данных Steam | ||||
| @@ -39,12 +39,11 @@ | ||||
| - [X] Добавить в карточки данные с AreWeAntiCheatYet | ||||
| - [X] Продублировать бейджи с карточки на страницу с деталями игры | ||||
| - [X] Добавить парсинг ярлыков из Steam | ||||
| - [X] Добавить парсинг ярлыков из EGS (скрыто для переработки) | ||||
| - [X] Добавить парсинг ярлыков из EGS | ||||
| - [ ] Избавиться от бинарника legendary | ||||
| - [X] Добавить запуск игр из EGS | ||||
| - [ ] Добавить скачивание игр из EGS | ||||
| - [ ] Добавить поддержку запуска сторонних игр из EGS | ||||
| - [ ] Добавить поддержку запуска игр с EOS | ||||
| - [ ] Добавить авторизацию в EGS через WebView вместо ручного ввода | ||||
| - [X] Получать описания для игр из EGS через их [API](https://store-content.ak.epicgames.com/api) | ||||
| - [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql) | ||||
| @@ -58,6 +57,7 @@ | ||||
| - [X] Добавить систему избранного для карточек | ||||
| - [X] Заменить все `print` на `logging` | ||||
| - [ ] Привести все логи к единому языку | ||||
| - [ ] Уменьшить количество строк для перевода | ||||
| - [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog) | ||||
| - [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py` | ||||
| - [X] Исправить частичное применение тем на лету | ||||
|   | ||||
| @@ -14,3 +14,5 @@ MIME-Version: | ||||
| Content-Type: | ||||
| Content-Transfer-Encoding: | ||||
| Generated-By: | ||||
| start.sh | ||||
| EGS | ||||
|   | ||||
| @@ -20,9 +20,9 @@ Current translation status: | ||||
|  | ||||
| | Locale | Progress | Translated | | ||||
| | :----- | -------: | ---------: | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 161 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 161 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 161 of 161 | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 194 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 194 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 194 of 194 | | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -20,9 +20,9 @@ | ||||
|  | ||||
| | Локаль | Прогресс | Переведено | | ||||
| | :----- | -------: | ---------: | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 161 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 161 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 161 из 161 | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 194 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 194 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 194 из 194 | | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -12,9 +12,42 @@ from collections.abc import Callable | ||||
| from portprotonqt.localization import get_egs_language, _ | ||||
| from portprotonqt.logger import get_logger | ||||
| from portprotonqt.image_utils import load_pixmap_async | ||||
| from portprotonqt.time_utils import parse_playtime_file, format_playtime, get_last_launch, get_last_launch_timestamp | ||||
| from portprotonqt.config_utils import get_portproton_location | ||||
| from portprotonqt.steam_api import ( | ||||
|     get_weanticheatyet_status_async, get_steam_apps_and_index_async, get_protondb_tier_async, | ||||
|     search_app, get_steam_home, get_last_steam_user, convert_steam_id, generate_thumbnail | ||||
| ) | ||||
| import vdf | ||||
| import shutil | ||||
| import zlib | ||||
| from portprotonqt.downloader import Downloader | ||||
| from PySide6.QtGui import QPixmap | ||||
|  | ||||
| logger = get_logger(__name__) | ||||
| downloader = Downloader() | ||||
|  | ||||
| def get_egs_executable(app_name: str, legendary_config_path: str) -> str | None: | ||||
|     """Получает путь к исполняемому файлу EGS-игры из installed.json с использованием orjson.""" | ||||
|     installed_json_path = os.path.join(legendary_config_path, "installed.json") | ||||
|     try: | ||||
|         with open(installed_json_path, "rb") as f: | ||||
|             installed_data = orjson.loads(f.read()) | ||||
|             if app_name in installed_data: | ||||
|                 install_path = installed_data[app_name].get("install_path", "").decode('utf-8') if isinstance(installed_data[app_name].get("install_path"), bytes) else installed_data[app_name].get("install_path", "") | ||||
|                 executable = installed_data[app_name].get("executable", "").decode('utf-8') if isinstance(installed_data[app_name].get("executable"), bytes) else installed_data[app_name].get("executable", "") | ||||
|                 if install_path and executable: | ||||
|                     return os.path.join(install_path, executable) | ||||
|             return None | ||||
|     except FileNotFoundError: | ||||
|         logger.error(f"installed.json not found at {installed_json_path}") | ||||
|         return None | ||||
|     except orjson.JSONDecodeError: | ||||
|         logger.error(f"Invalid JSON in {installed_json_path}") | ||||
|         return None | ||||
|     except Exception as e: | ||||
|         logger.error(f"Error reading installed.json: {e}") | ||||
|         return None | ||||
|  | ||||
| def get_cache_dir() -> Path: | ||||
|     """Returns the path to the cache directory, creating it if necessary.""" | ||||
| @@ -26,6 +59,237 @@ def get_cache_dir() -> Path: | ||||
|     cache_dir.mkdir(parents=True, exist_ok=True) | ||||
|     return cache_dir | ||||
|  | ||||
| def add_egs_to_steam(app_name: str, game_title: str, legendary_path: str, callback: Callable[[tuple[bool, str]], None]) -> None: | ||||
|     """ | ||||
|     Asynchronously adds an EGS game to Steam via shortcuts.vdf with PortProton tag. | ||||
|     Creates a launch script using legendary CLI with --no-wine and PortProton wrapper. | ||||
|     Wrapper is flatpak run if portproton_location is None or contains .var, otherwise start.sh. | ||||
|     Downloads Steam Grid covers if the game exists in Steam, and generates a thumbnail. | ||||
|     Calls the callback with (success, message). | ||||
|     """ | ||||
|     if not app_name or not app_name.strip() or not game_title or not game_title.strip(): | ||||
|         logger.error("Invalid app_name or game_title: empty or whitespace") | ||||
|         callback((False, "Game name or app name is empty or invalid")) | ||||
|         return | ||||
|  | ||||
|     if not os.path.exists(legendary_path): | ||||
|         logger.error(f"Legendary executable not found: {legendary_path}") | ||||
|         callback((False, f"Legendary executable not found: {legendary_path}")) | ||||
|         return | ||||
|  | ||||
|     portproton_dir = get_portproton_location() | ||||
|     if not portproton_dir: | ||||
|         logger.error("PortProton directory not found") | ||||
|         callback((False, "PortProton directory not found")) | ||||
|         return | ||||
|  | ||||
|     # Determine wrapper | ||||
|     wrapper = "flatpak run ru.linux_gaming.PortProton" | ||||
|     start_sh_path = os.path.join(portproton_dir, "data", "scripts", "start.sh") | ||||
|     if portproton_dir is not None and ".var" not in portproton_dir: | ||||
|         wrapper = start_sh_path | ||||
|         if not os.path.exists(start_sh_path): | ||||
|             logger.error(f"start.sh not found at {start_sh_path}") | ||||
|             callback((False, f"start.sh not found at {start_sh_path}")) | ||||
|             return | ||||
|  | ||||
|     # Create launch script | ||||
|     steam_scripts_dir = os.path.join(portproton_dir, "steam_scripts") | ||||
|     os.makedirs(steam_scripts_dir, exist_ok=True) | ||||
|     safe_game_name = re.sub(r'[<>:"/\\|?*]', '_', game_title.strip()) | ||||
|     script_path = os.path.join(steam_scripts_dir, f"{safe_game_name}_egs.sh") | ||||
|     legendary_config_path = os.path.dirname(legendary_path) | ||||
|  | ||||
|     script_content = f"""#!/usr/bin/env bash | ||||
| export LD_PRELOAD= | ||||
| export LEGENDARY_CONFIG_PATH="{legendary_config_path}" | ||||
| "{legendary_path}" launch {app_name} --no-wine --wrapper "env START_FROM_STEAM=1 {wrapper}" "$@" | ||||
| """ | ||||
|     try: | ||||
|         with open(script_path, "w", encoding="utf-8") as f: | ||||
|             f.write(script_content) | ||||
|         os.chmod(script_path, 0o755) | ||||
|         logger.info(f"Created launch script for EGS game: {script_path}") | ||||
|     except Exception as e: | ||||
|         logger.error(f"Failed to create launch script {script_path}: {e}") | ||||
|         callback((False, f"Failed to create launch script: {e}")) | ||||
|         return | ||||
|  | ||||
|     # Generate thumbnail | ||||
|     generated_icon_path = os.path.join(portproton_dir, "data", "img", f"{safe_game_name}_egs.png") | ||||
|     try: | ||||
|         img_dir = os.path.join(portproton_dir, "data", "img") | ||||
|         os.makedirs(img_dir, exist_ok=True) | ||||
|         game_exe = get_egs_executable(app_name, legendary_config_path) | ||||
|         if not game_exe or not os.path.exists(game_exe): | ||||
|             logger.warning(f"Executable not found for {app_name}, skipping thumbnail generation") | ||||
|             icon_path = "" | ||||
|         elif os.path.exists(generated_icon_path): | ||||
|             logger.info(f"Reusing existing thumbnail: {generated_icon_path}") | ||||
|             icon_path = generated_icon_path | ||||
|         else: | ||||
|             success = generate_thumbnail(game_exe, generated_icon_path, size=128, force_resize=True) | ||||
|             if not success or not os.path.exists(generated_icon_path): | ||||
|                 logger.warning(f"generate_thumbnail failed for {game_exe}") | ||||
|                 icon_path = "" | ||||
|             else: | ||||
|                 logger.info(f"Generated thumbnail: {generated_icon_path}") | ||||
|                 icon_path = generated_icon_path | ||||
|     except Exception as e: | ||||
|         logger.error(f"Error generating thumbnail for {app_name}: {e}") | ||||
|         icon_path = "" | ||||
|  | ||||
|     # Get Steam directories | ||||
|     steam_home = get_steam_home() | ||||
|     if not steam_home: | ||||
|         logger.error("Steam home directory not found") | ||||
|         callback((False, "Steam directory not found")) | ||||
|         return | ||||
|  | ||||
|     last_user = get_last_steam_user(steam_home) | ||||
|     if not last_user or 'SteamID' not in last_user: | ||||
|         logger.error("Failed to retrieve Steam user ID") | ||||
|         callback((False, "Failed to get Steam user ID")) | ||||
|         return | ||||
|  | ||||
|     userdata_dir = steam_home / "userdata" | ||||
|     user_id = last_user['SteamID'] | ||||
|     unsigned_id = convert_steam_id(user_id) | ||||
|     user_dir = userdata_dir / str(unsigned_id) | ||||
|     steam_shortcuts_path = user_dir / "config" / "shortcuts.vdf" | ||||
|     grid_dir = user_dir / "config" / "grid" | ||||
|     os.makedirs(grid_dir, exist_ok=True) | ||||
|  | ||||
|     # Backup shortcuts.vdf | ||||
|     backup_path = f"{steam_shortcuts_path}.backup" | ||||
|     if os.path.exists(steam_shortcuts_path): | ||||
|         try: | ||||
|             shutil.copy2(steam_shortcuts_path, backup_path) | ||||
|             logger.info(f"Created backup of shortcuts.vdf at {backup_path}") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Failed to create backup of shortcuts.vdf: {e}") | ||||
|             callback((False, f"Failed to create backup of shortcuts.vdf: {e}")) | ||||
|             return | ||||
|  | ||||
|     # Generate unique appid | ||||
|     unique_string = f"{script_path}{game_title}" | ||||
|     baseid = zlib.crc32(unique_string.encode('utf-8')) & 0xffffffff | ||||
|     appid = baseid | 0x80000000 | ||||
|     if appid > 0x7FFFFFFF: | ||||
|         aidvdf = appid - 0x100000000 | ||||
|     else: | ||||
|         aidvdf = appid | ||||
|  | ||||
|     steam_appid = None | ||||
|     downloaded_count = 0 | ||||
|     total_covers = 4 | ||||
|     download_lock = threading.Lock() | ||||
|  | ||||
|     def on_cover_download(cover_file: str, cover_type: str): | ||||
|         nonlocal downloaded_count | ||||
|         try: | ||||
|             if cover_file and os.path.exists(cover_file): | ||||
|                 logger.info(f"Downloaded cover {cover_type} to {cover_file}") | ||||
|             else: | ||||
|                 logger.warning(f"Failed to download cover {cover_type} for appid {steam_appid}") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error processing cover {cover_type} for appid {steam_appid}: {e}") | ||||
|         with download_lock: | ||||
|             downloaded_count += 1 | ||||
|             if downloaded_count == total_covers: | ||||
|                 finalize_shortcut() | ||||
|  | ||||
|     def finalize_shortcut(): | ||||
|         tags_dict = {'0': 'PortProton'} | ||||
|         shortcut = { | ||||
|             "appid": aidvdf, | ||||
|             "AppName": game_title, | ||||
|             "Exe": f'"{script_path}"', | ||||
|             "StartDir": f'"{os.path.dirname(script_path)}"', | ||||
|             "icon": icon_path, | ||||
|             "LaunchOptions": "", | ||||
|             "IsHidden": 0, | ||||
|             "AllowDesktopConfig": 1, | ||||
|             "AllowOverlay": 1, | ||||
|             "openvr": 0, | ||||
|             "Devkit": 0, | ||||
|             "DevkitGameID": "", | ||||
|             "LastPlayTime": 0, | ||||
|             "tags": tags_dict | ||||
|         } | ||||
|         logger.info(f"Shortcut entry for EGS game: {shortcut}") | ||||
|  | ||||
|         try: | ||||
|             if not os.path.exists(steam_shortcuts_path): | ||||
|                 os.makedirs(os.path.dirname(steam_shortcuts_path), exist_ok=True) | ||||
|                 open(steam_shortcuts_path, 'wb').close() | ||||
|  | ||||
|             try: | ||||
|                 if os.path.getsize(steam_shortcuts_path) > 0: | ||||
|                     with open(steam_shortcuts_path, 'rb') as f: | ||||
|                         shortcuts_data = vdf.binary_load(f) | ||||
|                 else: | ||||
|                     shortcuts_data = {"shortcuts": {}} | ||||
|             except Exception as load_err: | ||||
|                 logger.warning(f"Failed to load shortcuts.vdf, starting fresh: {load_err}") | ||||
|                 shortcuts_data = {"shortcuts": {}} | ||||
|  | ||||
|             shortcuts = shortcuts_data.get("shortcuts", {}) | ||||
|             for _key, entry in shortcuts.items(): | ||||
|                 if entry.get("AppName") == game_title and entry.get("Exe") == f'"{script_path}"': | ||||
|                     logger.info(f"EGS game '{game_title}' already exists in Steam shortcuts") | ||||
|                     callback((False, f"Game '{game_title}' already exists in Steam")) | ||||
|                     return | ||||
|  | ||||
|             new_index = str(len(shortcuts)) | ||||
|             shortcuts[new_index] = shortcut | ||||
|  | ||||
|             with open(steam_shortcuts_path, 'wb') as f: | ||||
|                 vdf.binary_dump({"shortcuts": shortcuts}, f) | ||||
|         except Exception as e: | ||||
|             logger.error(f"Failed to update shortcuts.vdf: {e}") | ||||
|             if os.path.exists(backup_path): | ||||
|                 try: | ||||
|                     shutil.copy2(backup_path, steam_shortcuts_path) | ||||
|                     logger.info("Restored shortcuts.vdf from backup") | ||||
|                 except Exception as restore_err: | ||||
|                     logger.error(f"Failed to restore shortcuts.vdf: {restore_err}") | ||||
|             callback((False, f"Failed to update shortcuts.vdf: {e}")) | ||||
|             return | ||||
|  | ||||
|         logger.info(f"EGS game '{game_title}' added to Steam") | ||||
|         callback((True, f"Game '{game_title}' added to Steam with covers")) | ||||
|  | ||||
|     def on_steam_apps(steam_data: tuple[list, dict]): | ||||
|         nonlocal steam_appid | ||||
|         steam_apps, steam_apps_index = steam_data | ||||
|         matching_app = search_app(game_title, steam_apps_index) | ||||
|         steam_appid = matching_app.get("appid") if matching_app else None | ||||
|  | ||||
|         if not steam_appid: | ||||
|             logger.info(f"No Steam appid found for EGS game {game_title}, skipping cover download") | ||||
|             finalize_shortcut() | ||||
|             return | ||||
|  | ||||
|         cover_types = [ | ||||
|             (".jpg", "header.jpg"), | ||||
|             ("p.jpg", "library_600x900_2x.jpg"), | ||||
|             ("_hero.jpg", "library_hero.jpg"), | ||||
|             ("_logo.png", "logo.png") | ||||
|         ] | ||||
|  | ||||
|         for suffix, cover_type in cover_types: | ||||
|             cover_file = os.path.join(grid_dir, f"{appid}{suffix}") | ||||
|             cover_url = f"https://cdn.cloudflare.steamstatic.com/steam/apps/{steam_appid}/{cover_type}" | ||||
|             downloader.download_async( | ||||
|                 cover_url, | ||||
|                 cover_file, | ||||
|                 timeout=5, | ||||
|                 callback=lambda result, cfile=cover_file, ctype=cover_type: on_cover_download(cfile, ctype) | ||||
|             ) | ||||
|  | ||||
|     get_steam_apps_and_index_async(on_steam_apps) | ||||
|  | ||||
| def get_egs_game_description_async( | ||||
|     app_name: str, | ||||
|     callback: Callable[[str], None], | ||||
| @@ -281,6 +545,7 @@ def get_egs_game_description_async( | ||||
|  | ||||
|     thread = threading.Thread(target=fetch_description, daemon=True) | ||||
|     thread.start() | ||||
|  | ||||
| def run_legendary_list_async(legendary_path: str, callback: Callable[[list | None], None]): | ||||
|     """ | ||||
|     Асинхронно выполняет команду 'legendary list --json' и возвращает результат через callback. | ||||
| @@ -326,6 +591,8 @@ def run_legendary_list_async(legendary_path: str, callback: Callable[[list | Non | ||||
| def load_egs_games_async(legendary_path: str, callback: Callable[[list[tuple]], None], downloader, update_progress: Callable[[int], None], update_status_message: Callable[[str, int], None]): | ||||
|     """ | ||||
|     Асинхронно загружает Epic Games Store игры с использованием legendary CLI. | ||||
|     Читает статистику времени игры и последнего запуска из файла statistics. | ||||
|     Проверяет наличие игры в Steam для получения ProtonDB статуса. | ||||
|     """ | ||||
|     logger.debug("Starting to load Epic Games Store games") | ||||
|     games: list[tuple] = [] | ||||
| @@ -334,6 +601,14 @@ def load_egs_games_async(legendary_path: str, callback: Callable[[list[tuple]], | ||||
|     cache_file = cache_dir / "legendary_games.json" | ||||
|     cache_ttl = 3600  # Cache TTL in seconds (1 hour) | ||||
|  | ||||
|     # Путь к файлу statistics | ||||
|     portproton_location = get_portproton_location() | ||||
|     if portproton_location is None: | ||||
|         logger.error("PortProton location is not set, cannot locate statistics file") | ||||
|         statistics_file = "" | ||||
|     else: | ||||
|         statistics_file = os.path.join(portproton_location, "data", "tmp", "statistics") | ||||
|  | ||||
|     if not os.path.exists(legendary_path): | ||||
|         logger.info("Legendary binary not found, downloading...") | ||||
|         def on_legendary_downloaded(result): | ||||
| @@ -345,7 +620,7 @@ def load_egs_games_async(legendary_path: str, callback: Callable[[list[tuple]], | ||||
|                     logger.error(f"Failed to make legendary binary executable: {e}") | ||||
|                     callback(games)  # Return empty games list on failure | ||||
|                     return | ||||
|                 _continue_loading_egs_games(legendary_path, callback, metadata_dir, cache_dir, cache_file, cache_ttl, update_progress, update_status_message) | ||||
|                 _continue_loading_egs_games(legendary_path, callback, metadata_dir, cache_dir, cache_file, cache_ttl, update_progress, update_status_message, statistics_file) | ||||
|             else: | ||||
|                 logger.error("Failed to download legendary binary") | ||||
|                 callback(games)  # Return empty games list on failure | ||||
| @@ -356,9 +631,9 @@ def load_egs_games_async(legendary_path: str, callback: Callable[[list[tuple]], | ||||
|             callback(games) | ||||
|         return | ||||
|     else: | ||||
|         _continue_loading_egs_games(legendary_path, callback, metadata_dir, cache_dir, cache_file, cache_ttl, update_progress, update_status_message) | ||||
|         _continue_loading_egs_games(legendary_path, callback, metadata_dir, cache_dir, cache_file, cache_ttl, update_progress, update_status_message, statistics_file) | ||||
|  | ||||
| def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tuple]], None], metadata_dir: Path, cache_dir: Path, cache_file: Path, cache_ttl: int, update_progress: Callable[[int], None], update_status_message: Callable[[str, int], None]): | ||||
| def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tuple]], None], metadata_dir: Path, cache_dir: Path, cache_file: Path, cache_ttl: int, update_progress: Callable[[int], None], update_status_message: Callable[[str, int], None], statistics_file: str): | ||||
|     """ | ||||
|     Продолжает процесс загрузки EGS игр, либо из кэша, либо через legendary CLI. | ||||
|     """ | ||||
| @@ -410,6 +685,33 @@ def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tu | ||||
|                         callback(final_games) | ||||
|                 return | ||||
|  | ||||
|             # Получаем путь к .exe для извлечения имени | ||||
|             game_exe = get_egs_executable(app_name, os.path.dirname(legendary_path)) | ||||
|             exe_name = "" | ||||
|             if game_exe: | ||||
|                 exe_name = os.path.splitext(os.path.basename(game_exe))[0] | ||||
|  | ||||
|             # Читаем статистику из файла statistics | ||||
|             playtime_seconds = 0 | ||||
|             formatted_playtime = "" | ||||
|             last_launch = _("Never") | ||||
|             last_launch_timestamp = 0 | ||||
|             if exe_name and os.path.exists(statistics_file): | ||||
|                 try: | ||||
|                     playtime_data = parse_playtime_file(statistics_file) | ||||
|                     matching_key = next( | ||||
|                         (key for key in playtime_data if os.path.basename(key).split('.')[0] == exe_name), | ||||
|                         None | ||||
|                     ) | ||||
|                     if matching_key: | ||||
|                         playtime_seconds = playtime_data[matching_key] | ||||
|                         formatted_playtime = format_playtime(playtime_seconds) | ||||
|                 except Exception as e: | ||||
|                     logger.error(f"Failed to parse playtime data for {app_name}: {e}") | ||||
|             if exe_name: | ||||
|                 last_launch = get_last_launch(exe_name) or _("Never") | ||||
|                 last_launch_timestamp = get_last_launch_timestamp(exe_name) | ||||
|  | ||||
|             metadata_file = metadata_dir / f"{app_name}.json" | ||||
|             cover_url = "" | ||||
|             try: | ||||
| @@ -426,40 +728,54 @@ def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tu | ||||
|             image_folder = os.path.join(os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")), "PortProtonQt", "images") | ||||
|             local_path = os.path.join(image_folder, f"{app_name}.jpg") if cover_url else "" | ||||
|  | ||||
|             def on_description_fetched(api_description: str): | ||||
|                 final_description = api_description or _("No description available") | ||||
|             def on_steam_apps(steam_data: tuple[list, dict]): | ||||
|                 steam_apps, steam_apps_index = steam_data | ||||
|                 matching_app = search_app(title, steam_apps_index) | ||||
|                 steam_appid = matching_app.get("appid") if matching_app else None | ||||
|  | ||||
|                 def on_cover_loaded(pixmap: QPixmap): | ||||
|                     from portprotonqt.steam_api import get_weanticheatyet_status_async | ||||
|                     def on_anticheat_status(status: str): | ||||
|                         nonlocal pending_images | ||||
|                         with results_lock: | ||||
|                             game_results[index] = ( | ||||
|                                 title, | ||||
|                                 final_description, | ||||
|                                 local_path if os.path.exists(local_path) else "", | ||||
|                                 app_name, | ||||
|                                 f"legendary:launch:{app_name}", | ||||
|                                 "", | ||||
|                                 _("Never"), | ||||
|                                 "", | ||||
|                                 "", | ||||
|                                 status or "", | ||||
|                                 0, | ||||
|                                 0, | ||||
|                                 "epic" | ||||
|                             ) | ||||
|                             pending_images -= 1 | ||||
|                             update_progress(total_games - pending_images) | ||||
|                             if pending_images == 0: | ||||
|                                 final_games = [game_results[i] for i in sorted(game_results.keys())] | ||||
|                                 callback(final_games) | ||||
|                 def on_protondb_tier(protondb_tier: str): | ||||
|                     def on_description_fetched(api_description: str): | ||||
|                         final_description = api_description or _("No description available") | ||||
|  | ||||
|                     get_weanticheatyet_status_async(title, on_anticheat_status) | ||||
|                         def on_cover_loaded(pixmap: QPixmap): | ||||
|                             def on_anticheat_status(status: str): | ||||
|                                 nonlocal pending_images | ||||
|                                 with results_lock: | ||||
|                                     game_results[index] = ( | ||||
|                                         title, | ||||
|                                         final_description, | ||||
|                                         local_path if os.path.exists(local_path) else "", | ||||
|                                         app_name, | ||||
|                                         f"legendary:launch:{app_name}", | ||||
|                                         "", | ||||
|                                         last_launch,  # Время последнего запуска | ||||
|                                         formatted_playtime,  # Форматированное время игры | ||||
|                                         protondb_tier,  # ProtonDB tier | ||||
|                                         status or "", | ||||
|                                         last_launch_timestamp,  # Временная метка последнего запуска | ||||
|                                         playtime_seconds,  # Время игры в секундах | ||||
|                                         "epic" | ||||
|                                     ) | ||||
|                                     pending_images -= 1 | ||||
|                                     update_progress(total_games - pending_images) | ||||
|                                     if pending_images == 0: | ||||
|                                         final_games = [game_results[i] for i in sorted(game_results.keys())] | ||||
|                                         callback(final_games) | ||||
|  | ||||
|                 load_pixmap_async(cover_url, 600, 900, on_cover_loaded, app_name=app_name) | ||||
|                             get_weanticheatyet_status_async(title, on_anticheat_status) | ||||
|  | ||||
|             get_egs_game_description_async(title, on_description_fetched) | ||||
|                         load_pixmap_async(cover_url, 600, 900, on_cover_loaded, app_name=app_name) | ||||
|  | ||||
|                     get_egs_game_description_async(title, on_description_fetched) | ||||
|  | ||||
|                 if steam_appid: | ||||
|                     logger.info(f"Found Steam appid {steam_appid} for EGS game {title}") | ||||
|                     get_protondb_tier_async(steam_appid, on_protondb_tier) | ||||
|                 else: | ||||
|                     logger.debug(f"No Steam app found for EGS game {title}") | ||||
|                     on_protondb_tier("")  # Proceed with empty ProtonDB tier | ||||
|  | ||||
|             get_steam_apps_and_index_async(on_steam_apps) | ||||
|  | ||||
|         max_workers = min(4, len(valid_games)) | ||||
|         with ThreadPoolExecutor(max_workers=max_workers) as executor: | ||||
|   | ||||
| @@ -171,7 +171,7 @@ class GameCard(QFrame): | ||||
|         self.steamLabel.setVisible(self.steam_visible) | ||||
|  | ||||
|         # Epic Games Store бейдж | ||||
|         egs_icon = self.theme_manager.get_icon("steam") | ||||
|         egs_icon = self.theme_manager.get_icon("epic_games") | ||||
|         self.egsLabel = ClickableLabel( | ||||
|             "Epic Games", | ||||
|             icon=egs_icon, | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-06-14 10:37+0500\n" | ||||
| "POT-Creation-Date: 2025-06-22 18:23+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: de_DE\n" | ||||
| @@ -26,31 +26,70 @@ msgstr "" | ||||
| msgid "Add to Favorites" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Import to Legendary" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Menu" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Menu" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary executable not found at {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Success" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "'{0}' was added to Steam. Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable file not found for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for EGS game '{0}'" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Select Game Installation Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "No folder selected" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Importing '{0}' to Legendary..." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -61,14 +100,51 @@ msgstr "" | ||||
| msgid "Removed '{0}' from favorites" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "PortProton is not found." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop for game: {0}" | ||||
| msgid "start.sh not found at {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to create .desktop file: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Desktop: {{0}}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to menu: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from menu: {{0}}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -76,7 +152,7 @@ msgid "Failed to parse .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Error reading .desktop file: {0}" | ||||
| msgid "Failed to read .desktop file: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -105,7 +181,7 @@ msgid "" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Could not locate .desktop file for '{0}'" | ||||
| msgid "Could not locate .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -121,35 +197,7 @@ msgid "Failed to delete custom data: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to menu: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from menu: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from Desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from Desktop" | ||||
| msgid "Failed to add game '{0}' to desktop: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Game name and executable path are required." | ||||
| @@ -174,25 +222,54 @@ msgstr "" | ||||
| msgid "Failed to copy cover image: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Restart Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "The game was added successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "The game was removed successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for '{0}'" | ||||
| msgid "" | ||||
| "'{0}' was removed from Steam. Please restart Steam for changes to take " | ||||
| "effect." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "PortProton directory not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Steam directory not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Failed to get Steam user ID" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Steam shortcuts file not found" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgid "Failed to create backup of shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to load shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' not found in Steam shortcuts" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to update shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove EGS game '{0}' from Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully opened folder for '{0}'" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Edit Game" | ||||
| @@ -235,10 +312,10 @@ msgstr "" | ||||
| msgid "Loading Epic Games Store games..." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "No description available" | ||||
| msgid "Never" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Never" | ||||
| msgid "No description available" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Supported" | ||||
| @@ -382,6 +459,21 @@ msgstr "" | ||||
| msgid "Gamepad haptic feedback:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Legendary Login" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Legendary Authentication:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Enter Legendary Authorization Code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Authorization Code:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Submit Code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Save Settings" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -397,6 +489,22 @@ msgstr "" | ||||
| msgid "Failed to open Legendary login page" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Please enter an authorization code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Successfully authenticated with Legendary" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary authentication failed: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Legendary executable not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Unexpected error during authentication" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Confirm Reset" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -478,6 +586,20 @@ msgstr "" | ||||
| msgid "Play" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable not found for EGS game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to launch game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Invalid command format (native)" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -488,12 +610,6 @@ msgstr "" | ||||
| msgid "File not found: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Reboot" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-06-14 10:37+0500\n" | ||||
| "POT-Creation-Date: 2025-06-22 18:23+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: es_ES\n" | ||||
| @@ -26,31 +26,70 @@ msgstr "" | ||||
| msgid "Add to Favorites" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Import to Legendary" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Menu" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Menu" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary executable not found at {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Success" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "'{0}' was added to Steam. Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable file not found for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for EGS game '{0}'" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Select Game Installation Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "No folder selected" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Importing '{0}' to Legendary..." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -61,14 +100,51 @@ msgstr "" | ||||
| msgid "Removed '{0}' from favorites" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "PortProton is not found." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop for game: {0}" | ||||
| msgid "start.sh not found at {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to create .desktop file: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Desktop: {{0}}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to menu: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from menu: {{0}}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -76,7 +152,7 @@ msgid "Failed to parse .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Error reading .desktop file: {0}" | ||||
| msgid "Failed to read .desktop file: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -105,7 +181,7 @@ msgid "" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Could not locate .desktop file for '{0}'" | ||||
| msgid "Could not locate .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -121,35 +197,7 @@ msgid "Failed to delete custom data: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to menu: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from menu: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from Desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from Desktop" | ||||
| msgid "Failed to add game '{0}' to desktop: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Game name and executable path are required." | ||||
| @@ -174,25 +222,54 @@ msgstr "" | ||||
| msgid "Failed to copy cover image: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Restart Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "The game was added successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "The game was removed successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for '{0}'" | ||||
| msgid "" | ||||
| "'{0}' was removed from Steam. Please restart Steam for changes to take " | ||||
| "effect." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "PortProton directory not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Steam directory not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Failed to get Steam user ID" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Steam shortcuts file not found" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgid "Failed to create backup of shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to load shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' not found in Steam shortcuts" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to update shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove EGS game '{0}' from Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully opened folder for '{0}'" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Edit Game" | ||||
| @@ -235,10 +312,10 @@ msgstr "" | ||||
| msgid "Loading Epic Games Store games..." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "No description available" | ||||
| msgid "Never" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Never" | ||||
| msgid "No description available" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Supported" | ||||
| @@ -382,6 +459,21 @@ msgstr "" | ||||
| msgid "Gamepad haptic feedback:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Legendary Login" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Legendary Authentication:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Enter Legendary Authorization Code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Authorization Code:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Submit Code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Save Settings" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -397,6 +489,22 @@ msgstr "" | ||||
| msgid "Failed to open Legendary login page" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Please enter an authorization code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Successfully authenticated with Legendary" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary authentication failed: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Legendary executable not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Unexpected error during authentication" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Confirm Reset" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -478,6 +586,20 @@ msgstr "" | ||||
| msgid "Play" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable not found for EGS game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to launch game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Invalid command format (native)" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -488,12 +610,6 @@ msgstr "" | ||||
| msgid "File not found: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Reboot" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PortProtonQt 0.1.1\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-06-14 10:37+0500\n" | ||||
| "POT-Creation-Date: 2025-06-22 18:23+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -24,31 +24,70 @@ msgstr "" | ||||
| msgid "Add to Favorites" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Import to Legendary" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Menu" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Menu" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary executable not found at {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Success" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "'{0}' was added to Steam. Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable file not found for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for EGS game '{0}'" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Select Game Installation Folder" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "No folder selected" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Importing '{0}' to Legendary..." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -59,14 +98,51 @@ msgstr "" | ||||
| msgid "Removed '{0}' from favorites" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "PortProton is not found." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop for game: {0}" | ||||
| msgid "start.sh not found at {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to create .desktop file: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Desktop: {{0}}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from Desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to menu: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from menu: {{0}}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -74,7 +150,7 @@ msgid "Failed to parse .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Error reading .desktop file: {0}" | ||||
| msgid "Failed to read .desktop file: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -103,7 +179,7 @@ msgid "" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Could not locate .desktop file for '{0}'" | ||||
| msgid "Could not locate .desktop file for game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| @@ -119,35 +195,7 @@ msgid "Failed to delete custom data: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to menu: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from menu: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from menu" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from Desktop: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from Desktop" | ||||
| msgid "Failed to add game '{0}' to desktop: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Game name and executable path are required." | ||||
| @@ -172,25 +220,54 @@ msgstr "" | ||||
| msgid "Failed to copy cover image: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Restart Steam" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "The game was added successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "" | ||||
| "The game was removed successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for '{0}'" | ||||
| msgid "" | ||||
| "'{0}' was removed from Steam. Please restart Steam for changes to take " | ||||
| "effect." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "PortProton directory not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Steam directory not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Failed to get Steam user ID" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Steam shortcuts file not found" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgid "Failed to create backup of shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to load shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' not found in Steam shortcuts" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to update shortcuts.vdf: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove EGS game '{0}' from Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Steam: {1}" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully opened folder for '{0}'" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Edit Game" | ||||
| @@ -233,10 +310,10 @@ msgstr "" | ||||
| msgid "Loading Epic Games Store games..." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "No description available" | ||||
| msgid "Never" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Never" | ||||
| msgid "No description available" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Supported" | ||||
| @@ -380,6 +457,21 @@ msgstr "" | ||||
| msgid "Gamepad haptic feedback:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Open Legendary Login" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Legendary Authentication:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Enter Legendary Authorization Code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Authorization Code:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Submit Code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Save Settings" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -395,6 +487,22 @@ msgstr "" | ||||
| msgid "Failed to open Legendary login page" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Please enter an authorization code" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Successfully authenticated with Legendary" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary authentication failed: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Legendary executable not found" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Unexpected error during authentication" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Confirm Reset" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -476,6 +584,20 @@ msgstr "" | ||||
| msgid "Play" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable not found for EGS game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to launch game: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Invalid command format (native)" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -486,12 +608,6 @@ msgstr "" | ||||
| msgid "File not found: {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Reboot" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,8 +9,8 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-06-14 10:37+0500\n" | ||||
| "PO-Revision-Date: 2025-06-14 10:37+0500\n" | ||||
| "POT-Creation-Date: 2025-06-22 18:23+0500\n" | ||||
| "PO-Revision-Date: 2025-06-22 18:22+0500\n" | ||||
| "Last-Translator: \n" | ||||
| "Language: ru_RU\n" | ||||
| "Language-Team: ru_RU <LL@li.org>\n" | ||||
| @@ -27,32 +27,73 @@ msgstr "Удалить из Избранного" | ||||
| msgid "Add to Favorites" | ||||
| msgstr "Добавить в Избранное" | ||||
|  | ||||
| msgid "Import to Legendary" | ||||
| msgstr "Импортировать игру" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgstr "Удалить из Steam" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgstr "Добавить в Steam" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "Открыть папку с игрой" | ||||
|  | ||||
| msgid "Remove from Desktop" | ||||
| msgstr "Удалить с рабочего стола" | ||||
|  | ||||
| msgid "Add to Desktop" | ||||
| msgstr "Добавить на рабочий стол" | ||||
|  | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "Редактировать" | ||||
|  | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "Удалить из PortProton" | ||||
|  | ||||
| msgid "Open Game Folder" | ||||
| msgstr "Открыть папку с игрой" | ||||
|  | ||||
| msgid "Remove from Menu" | ||||
| msgstr "Удалить из меню" | ||||
|  | ||||
| msgid "Add to Menu" | ||||
| msgstr "Добавить в меню" | ||||
|  | ||||
| msgid "Remove from Steam" | ||||
| msgstr "Удалить из Steam" | ||||
| msgid "Edit Shortcut" | ||||
| msgstr "Редактировать" | ||||
|  | ||||
| msgid "Add to Steam" | ||||
| msgstr "Добавить в Steam" | ||||
| msgid "Delete from PortProton" | ||||
| msgstr "Удалить из PortProton" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "Ошибка" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary executable not found at {0}" | ||||
| msgstr "Legendary не найден по пути {0}" | ||||
|  | ||||
| msgid "Success" | ||||
| msgstr "Успешно" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "'{0}' was added to Steam. Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
| "'{0}' был добавлен в Steam. Пожалуйста, перезапустите Steam, чтобы " | ||||
| "изменения вступили в силу." | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable file not found for game: {0}" | ||||
| msgstr "Не найден исполняемый файл для игры: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for EGS game '{0}'" | ||||
| msgstr "Открытие папки для игры EGS '{0}'" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgstr "Не удалось открыть папку для игры: {0}" | ||||
|  | ||||
| msgid "Select Game Installation Folder" | ||||
| msgstr "Выберите папку установки игры" | ||||
|  | ||||
| msgid "No folder selected" | ||||
| msgstr "Не выбрана ни одна папка" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Importing '{0}' to Legendary..." | ||||
| msgstr "Игра '{0}' импортирована" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Added '{0}' to favorites" | ||||
| @@ -62,23 +103,60 @@ msgstr "Добавление '{0}' в избранное" | ||||
| msgid "Removed '{0}' from favorites" | ||||
| msgstr "Удаление '{0}' из избранного" | ||||
|  | ||||
| msgid "Error" | ||||
| msgstr "Ошибка" | ||||
|  | ||||
| msgid "PortProton is not found." | ||||
| msgstr "PortProton не найден." | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop for game: {0}" | ||||
| msgstr "Не найдено ни одной исполняемой команды для игры: {0}" | ||||
| msgid "start.sh not found at {0}" | ||||
| msgstr "start.sh не найден по адресу {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to create .desktop file: {0}" | ||||
| msgstr "Не удалось создать файл .desktop: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "Игра '{0}' добавлена на рабочий стол" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "Не удалось добавить игру на рабочий стол: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Desktop: {{0}}" | ||||
| msgstr "Не удалось удалить игру '{0}' с рабочего стола: {{0}}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from Desktop" | ||||
| msgstr "Успешно удалена игра '{0}' с рабочего стола" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "Игра '{0}' добавлена в меню" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to menu: {1}" | ||||
| msgstr "Не удалось добавить игру в меню: '{0}' в меню: {1}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from menu: {{0}}" | ||||
| msgstr "Не удалось удалить игру '{0}' из меню: {{0}}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully removed game '{0}' from menu" | ||||
| msgstr "Успешно удалена игра '{0}' из меню" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "No executable command found in .desktop file for game: {0}" | ||||
| msgstr "В файле .desktop для игры не найдено ни одной исполняемой команды: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to parse .desktop file for game: {0}" | ||||
| msgstr "Не удалось удалить файл .desktop: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Error reading .desktop file: {0}" | ||||
| msgstr "Не удалось удалить файл .desktop: {0}" | ||||
| msgid "Failed to read .desktop file: {0}" | ||||
| msgstr "Не удалось прочитать файл .desktop: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid ".desktop file not found for game: {0}" | ||||
| @@ -108,8 +186,8 @@ msgstr "" | ||||
| ".desktop и настраиваемых данных." | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Could not locate .desktop file for '{0}'" | ||||
| msgstr "Не удалось найти файл .desktop для '{0}'" | ||||
| msgid "Could not locate .desktop file for game: {0}" | ||||
| msgstr "Не удалось найти файл .desktop для игры: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to delete .desktop file: {0}" | ||||
| @@ -124,36 +202,8 @@ msgid "Failed to delete custom data: {0}" | ||||
| msgstr "Не удалось удалить настраиваемые данные: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to menu" | ||||
| msgstr "Игра '{0}' добавлена в меню" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to menu: {0}" | ||||
| msgstr "Не удалось добавить игру в меню: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from menu: {0}" | ||||
| msgstr "Не удалось удалить игру из меню: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from menu" | ||||
| msgstr "Игра '{0}' удалена из меню" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' added to desktop" | ||||
| msgstr "Игра '{0}' добавлена на рабочий стол" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game to desktop: {0}" | ||||
| msgstr "Не удалось добавить игру на рабочий стол: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game from Desktop: {0}" | ||||
| msgstr "Не удалось удалить игру с рабочего стола: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' removed from Desktop" | ||||
| msgstr "Игра '{0}' удалена с рабочего стола" | ||||
| msgid "Failed to add game '{0}' to desktop: {1}" | ||||
| msgstr "Не удалось добавить игру '{0}' на рабочий стол: {1}" | ||||
|  | ||||
| msgid "Game name and executable path are required." | ||||
| msgstr "Необходимо указать название игры и путь к исполняемому файлу." | ||||
| @@ -177,30 +227,57 @@ msgstr "Не удалось удалить файл .desktop: {0}" | ||||
| msgid "Failed to copy cover image: {0}" | ||||
| msgstr "Не удалось удалить игру из меню: {0}" | ||||
|  | ||||
| msgid "Restart Steam" | ||||
| msgstr "Перезапустите Steam" | ||||
|  | ||||
| msgid "" | ||||
| "The game was added successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
| "Игра была успешно добавлена.\n" | ||||
| "Пожалуйста, перезапустите Steam, чтобы изменения вступили в силу." | ||||
|  | ||||
| msgid "" | ||||
| "The game was removed successfully.\n" | ||||
| "Please restart Steam for changes to take effect." | ||||
| msgstr "" | ||||
| "Игра была успешно удалена..\n" | ||||
| "Пожалуйста, перезапустите Steam, чтобы изменения вступили в силу." | ||||
| #, python-brace-format | ||||
| msgid "Failed to add game '{0}' to Steam: {1}" | ||||
| msgstr "Не удалось добавить игру '{0}' в Steam: {1}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Opened folder for '{0}'" | ||||
| msgstr "Открытие папки для '{0}'" | ||||
| msgid "" | ||||
| "'{0}' was removed from Steam. Please restart Steam for changes to take " | ||||
| "effect." | ||||
| msgstr "" | ||||
| "'{0}' был удалён из Steam. Пожалуйста, перезапустите Steam, чтобы " | ||||
| "изменения вступили в силу." | ||||
|  | ||||
| msgid "PortProton directory not found" | ||||
| msgstr "PortProton не найден." | ||||
|  | ||||
| msgid "Steam directory not found" | ||||
| msgstr "Каталог Steam не найден" | ||||
|  | ||||
| msgid "Failed to get Steam user ID" | ||||
| msgstr "Не удалось получить ID пользователя Steam" | ||||
|  | ||||
| msgid "Steam shortcuts file not found" | ||||
| msgstr "Файл ярлыков Steam не найден" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to open game folder: {0}" | ||||
| msgstr "Не удалось открыть папку для игры: {0}" | ||||
| msgid "Failed to create backup of shortcuts.vdf: {0}" | ||||
| msgstr "Не удалось создать резервную копию shortcuts.vdf: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to load shortcuts.vdf: {0}" | ||||
| msgstr "Не удалось загрузить shortcuts.vdf: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Game '{0}' not found in Steam shortcuts" | ||||
| msgstr "Игра '{0}' не найдена в ярлыках Steam" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to update shortcuts.vdf: {0}" | ||||
| msgstr "Не удалось обновить shortcuts.vdf: {0}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove EGS game '{0}' from Steam: {1}" | ||||
| msgstr "Не удалось удалить игру EGS '{0}' из Steam: {1}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to remove game '{0}' from Steam: {1}" | ||||
| msgstr "Не удалось удалить игру '{0}' из Steam: {1}" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Successfully opened folder for '{0}'" | ||||
| msgstr "Успешно открыта папка для '{0}'" | ||||
|  | ||||
| msgid "Edit Game" | ||||
| msgstr "Редактировать игру" | ||||
| @@ -242,12 +319,12 @@ msgstr "Запустить игру \"{name}\" с помощью PortProton" | ||||
| msgid "Loading Epic Games Store games..." | ||||
| msgstr "Загрузка игр из Epic Games Store..." | ||||
|  | ||||
| msgid "No description available" | ||||
| msgstr "Описание не найдено" | ||||
|  | ||||
| msgid "Never" | ||||
| msgstr "Никогда" | ||||
|  | ||||
| msgid "No description available" | ||||
| msgstr "Описание не найдено" | ||||
|  | ||||
| msgid "Supported" | ||||
| msgstr "Поддерживается" | ||||
|  | ||||
| @@ -389,6 +466,21 @@ msgstr "Тактильная отдача на геймпаде" | ||||
| msgid "Gamepad haptic feedback:" | ||||
| msgstr "Тактильная отдача на геймпаде:" | ||||
|  | ||||
| msgid "Open Legendary Login" | ||||
| msgstr "Открыть браузер для входа в Legendary" | ||||
|  | ||||
| msgid "Legendary Authentication:" | ||||
| msgstr "Авторизация в Legendary:" | ||||
|  | ||||
| msgid "Enter Legendary Authorization Code" | ||||
| msgstr "Введите код авторизации Legendary" | ||||
|  | ||||
| msgid "Authorization Code:" | ||||
| msgstr "Код авторизации;" | ||||
|  | ||||
| msgid "Submit Code" | ||||
| msgstr "Отправить код" | ||||
|  | ||||
| msgid "Save Settings" | ||||
| msgstr "Сохранить настройки" | ||||
|  | ||||
| @@ -404,6 +496,22 @@ msgstr "Открытие страницы входа в Legendary в брауз | ||||
| msgid "Failed to open Legendary login page" | ||||
| msgstr "Не удалось открыть страницу входа в Legendary" | ||||
|  | ||||
| msgid "Please enter an authorization code" | ||||
| msgstr "Пожалуйста, введите код авторизации" | ||||
|  | ||||
| msgid "Successfully authenticated with Legendary" | ||||
| msgstr "Успешная аутентификация в Legendary" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Legendary authentication failed: {0}" | ||||
| msgstr "Не удалось выполнить аутентификацию Legendary: {0}" | ||||
|  | ||||
| msgid "Legendary executable not found" | ||||
| msgstr "Не найден исполняемый файл Legendary" | ||||
|  | ||||
| msgid "Unexpected error during authentication" | ||||
| msgstr "Неожиданная ошибка при аутентификации" | ||||
|  | ||||
| msgid "Confirm Reset" | ||||
| msgstr "Подтвердите удаление" | ||||
|  | ||||
| @@ -487,6 +595,20 @@ msgstr "Остановить" | ||||
| msgid "Play" | ||||
| msgstr "Играть" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Executable not found for EGS game: {0}" | ||||
| msgstr "Не найден исполняемый файл для игры EGS: {0}" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "Невозможно запустить игру пока запущена другая" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "Идёт запуск" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Failed to launch game: {0}" | ||||
| msgstr "Не удалось запустить игру: {0}" | ||||
|  | ||||
| msgid "Invalid command format (native)" | ||||
| msgstr "Неправильный формат команды (нативная версия)" | ||||
|  | ||||
| @@ -497,12 +619,6 @@ msgstr "Неправильный формат команды (flatpak)" | ||||
| msgid "File not found: {0}" | ||||
| msgstr "Файл не найден: {0}" | ||||
|  | ||||
| msgid "Cannot launch game while another game is running" | ||||
| msgstr "Невозможно запустить игру пока запущена другая" | ||||
|  | ||||
| msgid "Launching" | ||||
| msgstr "Идёт запуск" | ||||
|  | ||||
| msgid "Reboot" | ||||
| msgstr "Перезагрузить" | ||||
|  | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from portprotonqt.system_overlay import SystemOverlay | ||||
|  | ||||
| from portprotonqt.image_utils import load_pixmap_async, round_corners, ImageCarousel | ||||
| from portprotonqt.steam_api import get_steam_game_info_async, get_full_steam_game_info_async, get_steam_installed_games | ||||
| from portprotonqt.egs_api import load_egs_games_async | ||||
| from portprotonqt.egs_api import load_egs_games_async, get_egs_executable | ||||
| from portprotonqt.theme_manager import ThemeManager, load_theme_screenshots, load_logo | ||||
| from portprotonqt.time_utils import save_last_launch, get_last_launch, parse_playtime_file, format_playtime, get_last_launch_timestamp, format_last_launch | ||||
| from portprotonqt.config_utils import ( | ||||
| @@ -325,19 +325,25 @@ class MainWindow(QMainWindow): | ||||
|                 self.update_status_message.emit | ||||
|             ) | ||||
|         elif display_filter == "favorites": | ||||
|             def on_all_games(portproton_games, steam_games): | ||||
|                 games = [game for game in portproton_games + steam_games if game[0] in favorites] | ||||
|             def on_all_games(portproton_games, steam_games, epic_games): | ||||
|                 games = [game for game in portproton_games + steam_games + epic_games if game[0] in favorites] | ||||
|                 self.games_loaded.emit(games) | ||||
|             self._load_portproton_games_async( | ||||
|                 lambda pg: self._load_steam_games_async( | ||||
|                     lambda sg: on_all_games(pg, sg) | ||||
|                     lambda sg: load_egs_games_async( | ||||
|                         self.legendary_path, | ||||
|                         lambda eg: on_all_games(pg, sg, eg), | ||||
|                         self.downloader, | ||||
|                         self.update_progress.emit, | ||||
|                         self.update_status_message.emit | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
|         else: | ||||
|             def on_all_games(portproton_games, steam_games): | ||||
|             def on_all_games(portproton_games, steam_games, epic_games): | ||||
|                 seen = set() | ||||
|                 games = [] | ||||
|                 for game in portproton_games + steam_games: | ||||
|                 for game in portproton_games + steam_games + epic_games: | ||||
|                     # Уникальный ключ: имя + exec_line | ||||
|                     key = (game[0], game[4]) | ||||
|                     if key not in seen: | ||||
| @@ -346,7 +352,13 @@ class MainWindow(QMainWindow): | ||||
|                 self.games_loaded.emit(games) | ||||
|             self._load_portproton_games_async( | ||||
|                 lambda pg: self._load_steam_games_async( | ||||
|                     lambda sg: on_all_games(pg, sg) | ||||
|                     lambda sg: load_egs_games_async( | ||||
|                         self.legendary_path, | ||||
|                         lambda eg: on_all_games(pg, sg, eg), | ||||
|                         self.downloader, | ||||
|                         self.update_progress.emit, | ||||
|                         self.update_status_message.emit | ||||
|                     ) | ||||
|                 ) | ||||
|             ) | ||||
|         return [] | ||||
| @@ -998,7 +1010,7 @@ class MainWindow(QMainWindow): | ||||
|  | ||||
|         # 3. Games display_filter | ||||
|         self.filter_keys = ["all", "steam", "portproton", "favorites", "epic"] | ||||
|         self.filter_labels = [_("all"), "steam", "portproton", _("favorites")] | ||||
|         self.filter_labels = [_("all"), "steam", "portproton", _("favorites"), "epic games store"] | ||||
|         self.gamesDisplayCombo = QComboBox() | ||||
|         self.gamesDisplayCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) | ||||
|         self.gamesDisplayCombo.addItems(self.filter_labels) | ||||
| @@ -1081,6 +1093,37 @@ class MainWindow(QMainWindow): | ||||
|         self.gamepadRumbleCheckBox.setChecked(current_rumble_state) | ||||
|         formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox) | ||||
|  | ||||
|         # 8. Legendary Authentication | ||||
|         self.legendaryAuthButton = AutoSizeButton( | ||||
|             _("Open Legendary Login"), | ||||
|             icon=self.theme_manager.get_icon("login") | ||||
|         ) | ||||
|         self.legendaryAuthButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|         self.legendaryAuthButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
|         self.legendaryAuthButton.clicked.connect(self.openLegendaryLogin) | ||||
|         self.legendaryAuthTitle = QLabel(_("Legendary Authentication:")) | ||||
|         self.legendaryAuthTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE) | ||||
|         self.legendaryAuthTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus) | ||||
|         formLayout.addRow(self.legendaryAuthTitle, self.legendaryAuthButton) | ||||
|  | ||||
|         self.legendaryCodeEdit = QLineEdit() | ||||
|         self.legendaryCodeEdit.setPlaceholderText(_("Enter Legendary Authorization Code")) | ||||
|         self.legendaryCodeEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE) | ||||
|         self.legendaryCodeEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
|         self.legendaryCodeTitle = QLabel(_("Authorization Code:")) | ||||
|         self.legendaryCodeTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE) | ||||
|         self.legendaryCodeTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus) | ||||
|         formLayout.addRow(self.legendaryCodeTitle, self.legendaryCodeEdit) | ||||
|  | ||||
|         self.submitCodeButton = AutoSizeButton( | ||||
|             _("Submit Code"), | ||||
|             icon=self.theme_manager.get_icon("save") | ||||
|         ) | ||||
|         self.submitCodeButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|         self.submitCodeButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
|         self.submitCodeButton.clicked.connect(self.submitLegendaryCode) | ||||
|         formLayout.addRow(QLabel(""), self.submitCodeButton) | ||||
|  | ||||
|         layout.addLayout(formLayout) | ||||
|  | ||||
|         # Кнопки | ||||
| @@ -1131,6 +1174,37 @@ class MainWindow(QMainWindow): | ||||
|             logger.error(f"Failed to open Legendary login page: {e}") | ||||
|             self.statusBar().showMessage(_("Failed to open Legendary login page"), 3000) | ||||
|  | ||||
|     def submitLegendaryCode(self): | ||||
|         """Submits the Legendary authorization code using the legendary CLI.""" | ||||
|         auth_code = self.legendaryCodeEdit.text().strip() | ||||
|         if not auth_code: | ||||
|             QMessageBox.warning(self, _("Error"), _("Please enter an authorization code")) | ||||
|             return | ||||
|  | ||||
|         try: | ||||
|             # Execute legendary auth command | ||||
|             result = subprocess.run( | ||||
|                 [self.legendary_path, "auth", "--code", auth_code], | ||||
|                 capture_output=True, | ||||
|                 text=True, | ||||
|                 check=True | ||||
|             ) | ||||
|             logger.info("Legendary authentication successful: %s", result.stdout) | ||||
|             self.statusBar().showMessage(_("Successfully authenticated with Legendary"), 3000) | ||||
|             self.legendaryCodeEdit.clear() | ||||
|             # Reload Epic Games Store games after successful authentication | ||||
|             self.games = self.loadGames() | ||||
|             self.updateGameGrid() | ||||
|         except subprocess.CalledProcessError as e: | ||||
|             logger.error("Legendary authentication failed: %s", e.stderr) | ||||
|             self.statusBar().showMessage(_("Legendary authentication failed: {0}").format(e.stderr), 5000) | ||||
|         except FileNotFoundError: | ||||
|             logger.error("Legendary executable not found at %s", self.legendary_path) | ||||
|             self.statusBar().showMessage(_("Legendary executable not found"), 5000) | ||||
|         except Exception as e: | ||||
|             logger.error("Unexpected error during Legendary authentication: %s", str(e)) | ||||
|             self.statusBar().showMessage(_("Unexpected error during authentication"), 5000) | ||||
|  | ||||
|     def resetSettings(self): | ||||
|         """Сбрасывает настройки и перезапускает приложение.""" | ||||
|         reply = QMessageBox.question( | ||||
| @@ -1526,7 +1600,7 @@ class MainWindow(QMainWindow): | ||||
|         steamLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://steamcommunity.com/app/{appid}"))) | ||||
|  | ||||
|         # Epic Games Store бейдж | ||||
|         egs_icon = self.theme_manager.get_icon("steam") | ||||
|         egs_icon = self.theme_manager.get_icon("epic_games") | ||||
|         egsLabel = ClickableLabel( | ||||
|             "Epic Games", | ||||
|             icon=egs_icon, | ||||
| @@ -1823,11 +1897,110 @@ class MainWindow(QMainWindow): | ||||
|         self.target_exe = None | ||||
|  | ||||
|     def toggleGame(self, exec_line, button=None): | ||||
|         # Обработка Steam-игр | ||||
|         if exec_line.startswith("steam://"): | ||||
|             url = QUrl(exec_line) | ||||
|             QDesktopServices.openUrl(url) | ||||
|             return | ||||
|  | ||||
|         # Обработка EGS-игр | ||||
|         if exec_line.startswith("legendary:launch:"): | ||||
|             app_name = exec_line.split("legendary:launch:")[1] | ||||
|  | ||||
|             # Получаем путь к .exe из installed.json | ||||
|             game_exe = get_egs_executable(app_name, self.legendary_config_path) | ||||
|             if not game_exe or not os.path.exists(game_exe): | ||||
|                 QMessageBox.warning(self, _("Error"), _("Executable not found for EGS game: {0}").format(app_name)) | ||||
|                 return | ||||
|  | ||||
|             current_exe = os.path.basename(game_exe) | ||||
|             if self.game_processes and self.target_exe is not None and self.target_exe != current_exe: | ||||
|                 QMessageBox.warning(self, _("Error"), _("Cannot launch game while another game is running")) | ||||
|                 return | ||||
|  | ||||
|             # Обновляем кнопку | ||||
|             update_button = button if button is not None else self.current_play_button | ||||
|             self.current_running_button = update_button | ||||
|             self.target_exe = current_exe | ||||
|             exe_name = os.path.splitext(current_exe)[0] | ||||
|  | ||||
|             # Проверяем, запущена ли игра | ||||
|             if self.game_processes and self.target_exe == current_exe: | ||||
|                 # Останавливаем игру | ||||
|                 if hasattr(self, 'input_manager'): | ||||
|                     self.input_manager.enable_gamepad_handling() | ||||
|  | ||||
|                 for proc in self.game_processes: | ||||
|                     try: | ||||
|                         parent = psutil.Process(proc.pid) | ||||
|                         children = parent.children(recursive=True) | ||||
|                         for child in children: | ||||
|                             try: | ||||
|                                 child.terminate() | ||||
|                             except psutil.NoSuchProcess: | ||||
|                                 pass | ||||
|                         psutil.wait_procs(children, timeout=5) | ||||
|                         for child in children: | ||||
|                             if child.is_running(): | ||||
|                                 child.kill() | ||||
|                         os.killpg(os.getpgid(proc.pid), signal.SIGTERM) | ||||
|                     except psutil.NoSuchProcess: | ||||
|                         pass | ||||
|                 self.game_processes = [] | ||||
|                 if update_button: | ||||
|                     update_button.setText(_("Play")) | ||||
|                     icon = self.theme_manager.get_icon("play") | ||||
|                     if isinstance(icon, str): | ||||
|                         icon = QIcon(icon) | ||||
|                     elif icon is None: | ||||
|                         icon = QIcon() | ||||
|                     update_button.setIcon(icon) | ||||
|                 if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None: | ||||
|                     self.checkProcessTimer.stop() | ||||
|                     self.checkProcessTimer.deleteLater() | ||||
|                     self.checkProcessTimer = None | ||||
|                 self.current_running_button = None | ||||
|                 self.target_exe = None | ||||
|                 self._gameLaunched = False | ||||
|             else: | ||||
|                 # Запускаем игру через PortProton | ||||
|                 env_vars = os.environ.copy() | ||||
|                 env_vars['START_FROM_STEAM'] = '1' | ||||
|                 env_vars['LEGENDARY_CONFIG_PATH'] = self.legendary_config_path | ||||
|  | ||||
|                 wrapper = "flatpak run ru.linux_gaming.PortProton" | ||||
|                 if self.portproton_location is not None and ".var" not in self.portproton_location: | ||||
|                     start_sh = os.path.join(self.portproton_location, "data", "scripts", "start.sh") | ||||
|                     wrapper = start_sh | ||||
|  | ||||
|                 cmd = [wrapper, game_exe] | ||||
|  | ||||
|                 try: | ||||
|                     process = subprocess.Popen(cmd, env=env_vars, shell=False, preexec_fn=os.setsid) | ||||
|                     self.game_processes.append(process) | ||||
|                     save_last_launch(exe_name, datetime.now()) | ||||
|                     if update_button: | ||||
|                         update_button.setText(_("Launching")) | ||||
|                         icon = self.theme_manager.get_icon("stop") | ||||
|                         if isinstance(icon, str): | ||||
|                             icon = QIcon(icon) | ||||
|                         elif icon is None: | ||||
|                             icon = QIcon() | ||||
|                         update_button.setIcon(icon) | ||||
|  | ||||
|                     # Delay disabling gamepad handling | ||||
|                     if hasattr(self, 'input_manager'): | ||||
|                         QTimer.singleShot(200, self.input_manager.disable_gamepad_handling) | ||||
|  | ||||
|                     self.checkProcessTimer = QTimer(self) | ||||
|                     self.checkProcessTimer.timeout.connect(self.checkTargetExe) | ||||
|                     self.checkProcessTimer.start(500) | ||||
|                 except Exception as e: | ||||
|                     logger.error(f"Failed to launch EGS game {app_name}: {e}") | ||||
|                     QMessageBox.warning(self, _("Error"), _("Failed to launch game: {0}").format(str(e))) | ||||
|             return | ||||
|  | ||||
|         # Обработка PortProton-игр | ||||
|         entry_exec_split = shlex.split(exec_line) | ||||
|         if entry_exec_split[0] == "env": | ||||
|             if len(entry_exec_split) < 3: | ||||
| @@ -1841,18 +2014,20 @@ class MainWindow(QMainWindow): | ||||
|             file_to_check = entry_exec_split[3] | ||||
|         else: | ||||
|             file_to_check = entry_exec_split[0] | ||||
|  | ||||
|         if not os.path.exists(file_to_check): | ||||
|             QMessageBox.warning(self, _("Error"), _("File not found: {0}").format(file_to_check)) | ||||
|             return | ||||
|         current_exe = os.path.basename(file_to_check) | ||||
|  | ||||
|         current_exe = os.path.basename(file_to_check) | ||||
|         if self.game_processes and self.target_exe is not None and self.target_exe != current_exe: | ||||
|             QMessageBox.warning(self, _("Error"), _("Cannot launch game while another game is running")) | ||||
|             return | ||||
|  | ||||
|         # Обновляем кнопку | ||||
|         update_button = button if button is not None else self.current_play_button | ||||
|  | ||||
|         # Если игра уже запущена для этого exe – останавливаем её по нажатию кнопки | ||||
|         # Если игра уже запущена для этого exe – останавливаем её | ||||
|         if self.game_processes and self.target_exe == current_exe: | ||||
|             if hasattr(self, 'input_manager'): | ||||
|                 self.input_manager.enable_gamepad_handling() | ||||
| @@ -1905,6 +2080,15 @@ class MainWindow(QMainWindow): | ||||
|                 env_vars['START_FROM_STEAM'] = '1' | ||||
|             elif entry_exec_split[0] == "flatpak": | ||||
|                 env_vars['START_FROM_STEAM'] = '1' | ||||
|             return | ||||
|  | ||||
|         # Запускаем игру | ||||
|         self.current_running_button = update_button | ||||
|         self.target_exe = current_exe | ||||
|         exe_name = os.path.splitext(current_exe)[0] | ||||
|         env_vars = os.environ.copy() | ||||
|         env_vars['START_FROM_STEAM'] = '1' | ||||
|         try: | ||||
|             process = subprocess.Popen(entry_exec_split, env=env_vars, shell=False, preexec_fn=os.setsid) | ||||
|             self.game_processes.append(process) | ||||
|             save_last_launch(exe_name, datetime.now()) | ||||
| @@ -1920,6 +2104,9 @@ class MainWindow(QMainWindow): | ||||
|             self.checkProcessTimer = QTimer(self) | ||||
|             self.checkProcessTimer.timeout.connect(self.checkTargetExe) | ||||
|             self.checkProcessTimer.start(500) | ||||
|         except Exception as e: | ||||
|             logger.error(f"Failed to launch game {exe_name}: {e}") | ||||
|             QMessageBox.warning(self, _("Error"), _("Failed to launch game: {0}").format(str(e))) | ||||
|  | ||||
|     def closeEvent(self, event): | ||||
|         """Завершает все дочерние процессы и сохраняет настройки при закрытии окна.""" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user