forked from Boria138/PortProtonQt
		
	Compare commits
	
		
			5 Commits
		
	
	
		
			e6e46d1aee
			...
			5f4748e177
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5f4748e177 | |||
| b9ae800e1f | |||
| d501ef039e | |||
| 3ab943bb60 | |||
| 4a48ec302d | 
| @@ -5,16 +5,14 @@ import shutil | ||||
| import subprocess | ||||
| import threading | ||||
| import logging | ||||
| import re | ||||
| import orjson | ||||
| import vdf | ||||
| from PySide6.QtWidgets import QMessageBox, QDialog, QMenu, QFileDialog | ||||
| from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt | ||||
| from PySide6.QtGui import QDesktopServices | ||||
| from portprotonqt.localization import _ | ||||
| from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites | ||||
| from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam, get_steam_home, get_last_steam_user, convert_steam_id | ||||
| from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable | ||||
| from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam | ||||
| from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam | ||||
| from portprotonqt.dialogs import AddGameDialog, generate_thumbnail | ||||
|  | ||||
| logger = logging.getLogger(__name__) | ||||
| @@ -893,124 +891,7 @@ Icon={icon_path} | ||||
|                 ) | ||||
|  | ||||
|         if game_source == "epic": | ||||
|             # For EGS games, construct the script path used in Steam shortcuts.vdf | ||||
|             if not self.portproton_location: | ||||
|                 self.signals.show_warning_dialog.emit( | ||||
|                     _("Error"), | ||||
|                     _("PortProton directory not found") | ||||
|                 ) | ||||
|                 return | ||||
|             steam_scripts_dir = os.path.join(self.portproton_location, "steam_scripts") | ||||
|             safe_game_name = re.sub(r'[<>:"/\\|?*]', '_', game_name.strip()) | ||||
|             script_path = os.path.join(steam_scripts_dir, f"{safe_game_name}_egs.sh") | ||||
|             quoted_script_path = f'"{script_path}"' | ||||
|  | ||||
|             # Directly remove the shortcut by matching AppName and Exe | ||||
|             try: | ||||
|                 steam_home = get_steam_home() | ||||
|                 if not steam_home: | ||||
|                     self.signals.show_warning_dialog.emit(_("Error"), _("Steam directory not found")) | ||||
|                     return | ||||
|  | ||||
|                 last_user = get_last_steam_user(steam_home) | ||||
|                 if not last_user or 'SteamID' not in last_user: | ||||
|                     self.signals.show_warning_dialog.emit(_("Error"), _("Failed to get Steam user ID")) | ||||
|                     return | ||||
|  | ||||
|                 userdata_dir = os.path.join(steam_home, "userdata") | ||||
|                 user_id = last_user['SteamID'] | ||||
|                 unsigned_id = convert_steam_id(user_id) | ||||
|                 user_dir = os.path.join(userdata_dir, str(unsigned_id)) | ||||
|                 steam_shortcuts_path = os.path.join(user_dir, "config", "shortcuts.vdf") | ||||
|                 backup_path = f"{steam_shortcuts_path}.backup" | ||||
|  | ||||
|                 if not os.path.exists(steam_shortcuts_path): | ||||
|                     self.signals.show_warning_dialog.emit( | ||||
|                         _("Error"), | ||||
|                         _("Steam shortcuts file not found") | ||||
|                     ) | ||||
|                     return | ||||
|  | ||||
|                 # Backup shortcuts.vdf | ||||
|                 try: | ||||
|                     shutil.copy2(steam_shortcuts_path, backup_path) | ||||
|                     logger.info("Created backup of shortcuts.vdf at %s", backup_path) | ||||
|                 except Exception as e: | ||||
|                     self.signals.show_warning_dialog.emit( | ||||
|                         _("Error"), | ||||
|                         _("Failed to create backup of shortcuts.vdf: {error}").format(error=str(e)) | ||||
|                     ) | ||||
|                     return | ||||
|  | ||||
|                 # Load shortcuts.vdf | ||||
|                 try: | ||||
|                     with open(steam_shortcuts_path, 'rb') as f: | ||||
|                         shortcuts_data = vdf.binary_load(f) | ||||
|                 except Exception as e: | ||||
|                     self.signals.show_warning_dialog.emit( | ||||
|                         _("Error"), | ||||
|                         _("Failed to load shortcuts.vdf: {error}").format(error=str(e)) | ||||
|                     ) | ||||
|                     return | ||||
|  | ||||
|                 shortcuts = shortcuts_data.get("shortcuts", {}) | ||||
|                 modified = False | ||||
|                 new_shortcuts = {} | ||||
|                 index = 0 | ||||
|  | ||||
|                 for _key, entry in shortcuts.items(): | ||||
|                     if entry.get("AppName") == game_name and entry.get("Exe") == quoted_script_path: | ||||
|                         modified = True | ||||
|                         logger.info("Removing EGS game '%s' from Steam shortcuts", game_name) | ||||
|                         continue | ||||
|                     new_shortcuts[str(index)] = entry | ||||
|                     index += 1 | ||||
|  | ||||
|                 if not modified: | ||||
|                     self.signals.show_warning_dialog.emit( | ||||
|                         _("Error"), | ||||
|                         _("Game '{game_name}' not found in Steam shortcuts").format(game_name=game_name) | ||||
|                     ) | ||||
|                     return | ||||
|  | ||||
|                 # Save updated shortcuts.vdf | ||||
|                 try: | ||||
|                     with open(steam_shortcuts_path, 'wb') as f: | ||||
|                         vdf.binary_dump({"shortcuts": new_shortcuts}, f) | ||||
|                     logger.info("Updated shortcuts.vdf, removed '%s'", game_name) | ||||
|                     on_remove_from_steam_result((True, "Game '{game_name}' was removed from Steam. Please restart Steam for changes to take effect.")) | ||||
|                 except Exception as e: | ||||
|                     self.signals.show_warning_dialog.emit( | ||||
|                         _("Error"), | ||||
|                         _("Failed to update shortcuts.vdf: {error}").format(error=str(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("Failed to restore shortcuts.vdf: %s", restore_err) | ||||
|                     on_remove_from_steam_result((False, "Failed to update shortcuts.vdf: {error}")) | ||||
|                     return | ||||
|  | ||||
|                 # Optionally, remove the script file | ||||
|                 if os.path.exists(script_path): | ||||
|                     try: | ||||
|                         os.remove(script_path) | ||||
|                         logger.info("Removed EGS script: %s", script_path) | ||||
|                     except OSError as e: | ||||
|                         logger.warning(f"Failed to remove EGS script '{script_path}': {str(e)}") | ||||
|  | ||||
|             except Exception as e: | ||||
|                 self.signals.show_warning_dialog.emit( | ||||
|                     _("Error"), | ||||
|                     _("Failed to remove EGS game '{game_name}' from Steam: {error}").format( | ||||
|                         game_name=game_name, error=str(e) | ||||
|                     ) | ||||
|                 ) | ||||
|                 on_remove_from_steam_result((False, "Failed to remove EGS game '{game_name}' from Steam: {error}")) | ||||
|                 return | ||||
|  | ||||
|             remove_egs_from_steam(game_name, self.portproton_location, on_remove_from_steam_result) | ||||
|         else: | ||||
|             # For non-EGS games, use steam_api | ||||
|             exec_line = self._get_exec_line(game_name, exec_line) | ||||
|   | ||||
| @@ -13,7 +13,8 @@ from portprotonqt.config_utils import get_portproton_location | ||||
| from portprotonqt.localization import _ | ||||
| from portprotonqt.logger import get_logger | ||||
| import portprotonqt.themes.standart.styles as default_styles | ||||
| from portprotonqt.themes.standart.styles import FileExplorerStyles | ||||
| from portprotonqt.theme_manager import ThemeManager | ||||
| from portprotonqt.custom_widgets import AutoSizeButton | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from portprotonqt.main_window import MainWindow | ||||
| @@ -88,8 +89,10 @@ class FileSelectedSignal(QObject): | ||||
|     file_selected = Signal(str)  # Сигнал с путем к выбранному файлу | ||||
|  | ||||
| class FileExplorer(QDialog): | ||||
|     def __init__(self, parent=None, file_filter=None): | ||||
|     def __init__(self, parent=None, theme=None, file_filter=None): | ||||
|         super().__init__(parent) | ||||
|         self.theme = theme if theme else default_styles | ||||
|         self.theme_manager = ThemeManager() | ||||
|         self.file_signal = FileSelectedSignal() | ||||
|         self.file_filter = file_filter  # Store the file filter | ||||
|         self.mime_db = QMimeDatabase()  # Initialize QMimeDatabase for mimetype detection | ||||
| @@ -98,7 +101,6 @@ class FileExplorer(QDialog): | ||||
|         # Настройки окна | ||||
|         self.setWindowModality(Qt.WindowModality.ApplicationModal) | ||||
|         self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) | ||||
|         self.setStyleSheet(FileExplorerStyles.WINDOW_STYLE) | ||||
|  | ||||
|         # Find InputManager from parent | ||||
|         self.input_manager = None | ||||
| @@ -153,28 +155,28 @@ class FileExplorer(QDialog): | ||||
|         self.drives_container = QWidget() | ||||
|         self.drives_container.setLayout(self.drives_layout) | ||||
|         self.drives_scroll.setWidget(self.drives_container) | ||||
|         self.drives_scroll.setStyleSheet(FileExplorerStyles.BUTTON_STYLE) | ||||
|         self.drives_scroll.setFixedHeight(50) | ||||
|         self.drives_scroll.setStyleSheet(self.theme.SCROLL_AREA_STYLE) | ||||
|         self.drives_scroll.setFixedHeight(60) | ||||
|         self.main_layout.addWidget(self.drives_scroll) | ||||
|  | ||||
|         # Путь | ||||
|         self.path_label = QLabel() | ||||
|         self.path_label.setStyleSheet(FileExplorerStyles.PATH_LABEL_STYLE) | ||||
|         self.path_label.setStyleSheet(self.theme.FILE_EXPLORER_PATH_LABEL_STYLE) | ||||
|         self.main_layout.addWidget(self.path_label) | ||||
|  | ||||
|         # Список файлов | ||||
|         self.file_list = QListWidget() | ||||
|         self.file_list.setStyleSheet(FileExplorerStyles.LIST_STYLE) | ||||
|         self.file_list.setStyleSheet(self.theme.FILE_EXPLORER_STYLE) | ||||
|         self.file_list.itemClicked.connect(self.handle_item_click) | ||||
|         self.main_layout.addWidget(self.file_list) | ||||
|  | ||||
|         # Кнопки | ||||
|         self.button_layout = QHBoxLayout() | ||||
|         self.button_layout.setSpacing(10) | ||||
|         self.select_button = QPushButton(_("Select")) | ||||
|         self.cancel_button = QPushButton(_("Cancel")) | ||||
|         self.select_button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE) | ||||
|         self.cancel_button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE) | ||||
|         self.select_button = AutoSizeButton(_("Select"), icon=self.theme_manager.get_icon("apply")) | ||||
|         self.cancel_button = AutoSizeButton(_("Cancel"), icon=self.theme_manager.get_icon("cancel")) | ||||
|         self.select_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|         self.cancel_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|         self.button_layout.addWidget(self.select_button) | ||||
|         self.button_layout.addWidget(self.cancel_button) | ||||
|         self.main_layout.addLayout(self.button_layout) | ||||
| @@ -246,8 +248,8 @@ class FileExplorer(QDialog): | ||||
|         drives = self.get_mounted_drives() | ||||
|         for drive in drives: | ||||
|             drive_name = os.path.basename(drive) or drive.split('/')[-1] or drive | ||||
|             button = QPushButton(drive_name) | ||||
|             button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE) | ||||
|             button = AutoSizeButton(drive_name, icon=self.theme_manager.get_icon("mount_point")) | ||||
|             button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE) | ||||
|             button.clicked.connect(lambda checked, path=drive: self.change_drive(path)) | ||||
|             self.drives_layout.addWidget(button) | ||||
|         self.drives_layout.addStretch() | ||||
| @@ -266,7 +268,13 @@ class FileExplorer(QDialog): | ||||
|         try: | ||||
|             if self.current_path != "/": | ||||
|                 item = QListWidgetItem("../") | ||||
|                 item.setIcon(QIcon.fromTheme("folder-symbolic")) | ||||
|                 folder_icon = self.theme_manager.get_icon("folder") | ||||
|                 # Ensure the icon is a QIcon | ||||
|                 if isinstance(folder_icon, str) and os.path.isfile(folder_icon): | ||||
|                     folder_icon = QIcon(folder_icon) | ||||
|                 elif not isinstance(folder_icon, QIcon): | ||||
|                     folder_icon = QIcon()  # Fallback to empty icon | ||||
|                 item.setIcon(folder_icon) | ||||
|                 self.file_list.addItem(item) | ||||
|  | ||||
|             items = os.listdir(self.current_path) | ||||
| @@ -282,7 +290,14 @@ class FileExplorer(QDialog): | ||||
|  | ||||
|             for d in sorted(dirs): | ||||
|                 item = QListWidgetItem(f"{d}/") | ||||
|                 item.setIcon(QIcon.fromTheme("folder-symbolic")) | ||||
|                 folder_icon = self.theme_manager.get_icon("folder") | ||||
|                 # Ensure the icon is a QIcon | ||||
|                 if isinstance(folder_icon, str) and os.path.isfile(folder_icon): | ||||
|                     folder_icon = QIcon(folder_icon) | ||||
|                 elif not isinstance(folder_icon, QIcon): | ||||
|                     folder_icon = QIcon()  # Fallback to empty icon | ||||
|                 item.setIcon(folder_icon) | ||||
|                 self.file_list.setFocusPolicy(Qt.FocusPolicy.NoFocus) | ||||
|                 self.file_list.addItem(item) | ||||
|  | ||||
|             for f in sorted(files): | ||||
| @@ -294,8 +309,6 @@ class FileExplorer(QDialog): | ||||
|                     pixmap = QPixmap(file_path) | ||||
|                     if not pixmap.isNull(): | ||||
|                         item.setIcon(QIcon(pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio))) | ||||
|                     else: | ||||
|                         item.setIcon(QIcon.fromTheme("image-x-generic-symbolic")) | ||||
|                 elif file_path.lower().endswith(".exe"): | ||||
|                     tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False) | ||||
|                     tmp.close() | ||||
| @@ -303,15 +316,7 @@ class FileExplorer(QDialog): | ||||
|                         pixmap = QPixmap(tmp.name) | ||||
|                         if not pixmap.isNull(): | ||||
|                             item.setIcon(QIcon(pixmap)) | ||||
|                         else: | ||||
|                             item.setIcon(QIcon.fromTheme("application-x-executable-symbolic")) | ||||
|                         os.unlink(tmp.name) | ||||
|                     else: | ||||
|                         item.setIcon(QIcon.fromTheme("application-x-executable-symbolic")) | ||||
|                 else: | ||||
|                     icon_name = self.mime_db.mimeTypeForFile(file_path).iconName() | ||||
|                     symbolic_icon_name = icon_name + "-symbolic" if icon_name else "text-x-generic-symbolic" | ||||
|                     item.setIcon(QIcon.fromTheme(symbolic_icon_name, QIcon.fromTheme("text-x-generic-symbolic"))) | ||||
|  | ||||
|                 self.file_list.addItem(item) | ||||
|  | ||||
|   | ||||
| @@ -27,18 +27,12 @@ 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.""" | ||||
| def read_installed_json(legendary_config_path: str) -> dict | None: | ||||
|     """Читает installed.json и возвращает словарь с данными или None в случае ошибки.""" | ||||
|     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 | ||||
|             return orjson.loads(f.read()) | ||||
|     except FileNotFoundError: | ||||
|         logger.error(f"installed.json not found at {installed_json_path}") | ||||
|         return None | ||||
| @@ -49,6 +43,17 @@ def get_egs_executable(app_name: str, legendary_config_path: str) -> str | None: | ||||
|         logger.error(f"Error reading installed.json: {e}") | ||||
|         return None | ||||
|  | ||||
| def get_egs_executable(app_name: str, legendary_config_path: str) -> str | None: | ||||
|     """Получает путь к исполняемому файлу EGS-игры из installed.json.""" | ||||
|     installed_data = read_installed_json(legendary_config_path) | ||||
|     if installed_data is None or app_name not in installed_data: | ||||
|         return None | ||||
|     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 | ||||
|  | ||||
| def get_cache_dir() -> Path: | ||||
|     """Returns the path to the cache directory, creating it if necessary.""" | ||||
|     xdg_cache_home = os.getenv( | ||||
| @@ -59,6 +64,108 @@ def get_cache_dir() -> Path: | ||||
|     cache_dir.mkdir(parents=True, exist_ok=True) | ||||
|     return cache_dir | ||||
|  | ||||
| def remove_egs_from_steam(game_name: str, portproton_dir: str, callback: Callable[[tuple[bool, str]], None]) -> None: | ||||
|     """ | ||||
|     Removes an EGS game from Steam by modifying shortcuts.vdf and deleting the launch script. | ||||
|     Calls the callback with (success, message). | ||||
|  | ||||
|     Args: | ||||
|         game_name: The display name of the game. | ||||
|         portproton_dir: Path to the PortProton directory. | ||||
|         callback: Callback function to handle the result (success, message). | ||||
|     """ | ||||
|     if not portproton_dir: | ||||
|         logger.error("PortProton directory not found") | ||||
|         callback((False, "PortProton directory not found")) | ||||
|         return | ||||
|  | ||||
|     steam_scripts_dir = os.path.join(portproton_dir, "steam_scripts") | ||||
|     safe_game_name = re.sub(r'[<>:"/\\|?*]', '_', game_name.strip()) | ||||
|     script_path = os.path.join(steam_scripts_dir, f"{safe_game_name}_egs.sh") | ||||
|     quoted_script_path = f'"{script_path}"' | ||||
|  | ||||
|     steam_home = get_steam_home() | ||||
|     if not steam_home: | ||||
|         logger.error("Steam 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 = os.path.join(steam_home, "userdata") | ||||
|     user_id = last_user['SteamID'] | ||||
|     unsigned_id = convert_steam_id(user_id) | ||||
|     user_dir = os.path.join(userdata_dir, str(unsigned_id)) | ||||
|     steam_shortcuts_path = os.path.join(user_dir, "config", "shortcuts.vdf") | ||||
|     backup_path = f"{steam_shortcuts_path}.backup" | ||||
|  | ||||
|     if not os.path.exists(steam_shortcuts_path): | ||||
|         logger.error("Steam shortcuts file not found") | ||||
|         callback((False, "Steam shortcuts file not found")) | ||||
|         return | ||||
|  | ||||
|     try: | ||||
|         shutil.copy2(steam_shortcuts_path, backup_path) | ||||
|         logger.info("Created backup of shortcuts.vdf at %s", 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 | ||||
|  | ||||
|     try: | ||||
|         with open(steam_shortcuts_path, 'rb') as f: | ||||
|             shortcuts_data = vdf.binary_load(f) | ||||
|     except Exception as e: | ||||
|         logger.error(f"Failed to load shortcuts.vdf: {e}") | ||||
|         callback((False, f"Failed to load shortcuts.vdf: {e}")) | ||||
|         return | ||||
|  | ||||
|     shortcuts = shortcuts_data.get("shortcuts", {}) | ||||
|     modified = False | ||||
|     new_shortcuts = {} | ||||
|     index = 0 | ||||
|  | ||||
|     for _key, entry in shortcuts.items(): | ||||
|         if entry.get("AppName") == game_name and entry.get("Exe") == quoted_script_path: | ||||
|             modified = True | ||||
|             logger.info("Removing EGS game '%s' from Steam shortcuts", game_name) | ||||
|             continue | ||||
|         new_shortcuts[str(index)] = entry | ||||
|         index += 1 | ||||
|  | ||||
|     if not modified: | ||||
|         logger.error("Game '%s' not found in Steam shortcuts", game_name) | ||||
|         callback((False, f"Game '{game_name}' not found in Steam shortcuts")) | ||||
|         return | ||||
|  | ||||
|     try: | ||||
|         with open(steam_shortcuts_path, 'wb') as f: | ||||
|             vdf.binary_dump({"shortcuts": new_shortcuts}, f) | ||||
|         logger.info("Updated shortcuts.vdf, removed '%s'", game_name) | ||||
|     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 | ||||
|  | ||||
|     if os.path.exists(script_path): | ||||
|         try: | ||||
|             os.remove(script_path) | ||||
|             logger.info("Removed EGS script: %s", script_path) | ||||
|         except OSError as e: | ||||
|             logger.warning(f"Failed to remove EGS script '{script_path}': {str(e)}") | ||||
|  | ||||
|     callback((True, f"Game '{game_name}' was removed from Steam. Please restart Steam for changes to take effect.")) | ||||
|  | ||||
| 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. | ||||
|   | ||||
							
								
								
									
										1
									
								
								portprotonqt/themes/standart/images/icons/apply.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								portprotonqt/themes/standart/images/icons/apply.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m5.8957 13.164-4.8957-4.8957 1.2239-1.2239 3.6718 3.6718 7.8804-7.8804 1.2239 1.2239z" fill="#fff" stroke-width=".014444"/></svg> | ||||
| After Width: | Height: | Size: 257 B | 
							
								
								
									
										3
									
								
								portprotonqt/themes/standart/images/icons/folder.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								portprotonqt/themes/standart/images/icons/folder.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| <svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><style id="current-color-scheme" type="text/css">.ColorScheme-Text { color: #fcfcfc; } </style><style type="text/css"> | ||||
|    .ColorScheme-Text { color:#4d4d4d; } | ||||
|   </style></defs><path d="m7.7202 1c-3.1678 0-4.7522-1.88e-4 -5.7363 0.98394-0.88825 0.88825-0.97414 2.2778-0.98256 4.8669 0.21843-0.66601 0.73947-0.95582 1.4337-0.95582h2.4746c3.4015-0.41346 2.8119-2.0399 4.9766-2.0399h3.6416c0.60291 0 1.1406 0.22096 1.407 0.75012-0.09519-1.227-0.33057-2.0331-0.9188-2.6213-0.98413-0.98412-2.5685-0.98394-5.7363-0.98394zm5.8056 4.0496-3.6409 6.86e-4c-2.1647 0-1.5751 1.5271-4.9766 1.9151h-2.4746c-0.69375 0-1.2145 0.27113-1.433 0.8948-5e-5 0.10542-6.86e-4 0.19915-6.86e-4 0.30855v0.52454c0 2.9728-1.88e-4 4.4597 0.98394 5.3832 0.98413 0.92352 2.5685 0.9236 5.7363 0.9236h0.55951c3.1678 0 4.7522-7.4e-5 5.7363-0.9236 0.98412-0.92354 0.98394-2.4104 0.98394-5.3832v-0.52454c0-0.92272-0.0035-1.6886-0.03291-2.3525-0.25332-0.54334-0.81238-0.76658-1.4413-0.76658z" fill="#fff" stroke-width=".13562"/></svg> | ||||
| After Width: | Height: | Size: 1.1 KiB | 
| @@ -0,0 +1,3 @@ | ||||
| <svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><style id="current-color-scheme" type="text/css">.ColorScheme-Text { color: #fcfcfc; } </style><style type="text/css"> | ||||
|    .ColorScheme-Text { color:#4d4d4d; } | ||||
|   </style></defs><path d="m3.1 10.8a1.4 1.4 0 0 0-1.4 1.4v1.4a1.4 1.4 0 0 0 1.4 1.4h9.8a1.4 1.4 0 0 0 1.4-1.4v-1.4a1.4 1.4 0 0 0-1.4-1.4zm2.1 2.625h-1.4a0.525 0.525 0 0 1 0-1.05h1.4a0.525 0.525 0 0 1 0 1.05zm9.1-3.6354a2.7673 2.7673 0 0 0-1.4-0.38955h-9.8a2.7673 2.7673 0 0 0-1.4 0.38955v-7.3896a1.4 1.4 0 0 1 1.4-1.4h9.8a1.4 1.4 0 0 1 1.4 1.4z" fill="#fff" stroke-width=".7"/></svg> | ||||
| After Width: | Height: | Size: 667 B | 
| @@ -856,6 +856,72 @@ SETTINGS_CHECKBOX_STYLE = f""" | ||||
|     }} | ||||
| """ | ||||
|  | ||||
| FILE_EXPLORER_STYLE = f""" | ||||
|     QListView {{ | ||||
|         font-size: {font_size_a}; | ||||
|         font-family: {font_family}; | ||||
|         background-color: {color_c}; | ||||
|         color: {color_f}; | ||||
|         border-top-left-radius: 5px; | ||||
|         border-bottom-left-radius: 5px; | ||||
|     }} | ||||
|     QListView::item {{ | ||||
|         padding: 8px; | ||||
|         margin: 0px 5px; | ||||
|         border-bottom: {border_b} rgba(255, 255, 255, 0.1); | ||||
|     }} | ||||
|     QListView::item:selected {{ | ||||
|         background-color: {color_a}; | ||||
|         color: {color_f}; | ||||
|         border-radius: {border_radius_a}; | ||||
|     }} | ||||
|     QListView::item:hover {{ | ||||
|         background-color: {color_a}; | ||||
|         color: {color_f}; | ||||
|         border-radius: {border_radius_a}; | ||||
|     }} | ||||
|     QListView::item:focus {{ | ||||
|         background-color: {color_a}; | ||||
|         color: {color_f}; | ||||
|         border-radius: {border_radius_a}; | ||||
|     }} | ||||
|     QScrollBar:vertical {{ | ||||
|         width: 10px; | ||||
|         border:  {border_a}; | ||||
|         border-radius: 5px; | ||||
|         background: {color_c}; | ||||
|     }} | ||||
|     QScrollBar::handle:vertical {{ | ||||
|         background: #bebebe; | ||||
|         border:  {border_a}; | ||||
|         border-radius: 5px; | ||||
|     }} | ||||
|     QScrollBar::add-line:vertical {{ | ||||
|         border:  {border_a}; | ||||
|         background: {color_c}; | ||||
|         border-bottom-right-radius: 5px; | ||||
|     }} | ||||
|     QScrollBar::sub-line:vertical {{ | ||||
|         border:  {border_a}; | ||||
|         background: {color_c}; | ||||
|         border-top-right-radius: 5px; | ||||
|     }} | ||||
|     QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical {{ | ||||
|         border:  {border_a}; | ||||
|         width: 3px; | ||||
|         height: 3px; | ||||
|         background: none; | ||||
|     }} | ||||
| """ | ||||
|  | ||||
| FILE_EXPLORER_PATH_LABEL_STYLE = f""" | ||||
|     QLabel {{ | ||||
|         color: {color_a}; | ||||
|         font-size: {font_size_a}; | ||||
|         font-family: {font_family}; | ||||
|     }} | ||||
| """ | ||||
|  | ||||
| # ФУНКЦИЯ ДЛЯ ДИНАМИЧЕСКОГО ГРАДИЕНТА (ДЕТАЛИ ИГР) | ||||
| # Функции из этой темы срабатывает всегда вне зависимости от выбранной темы, функции из других тем работают только в этих темах | ||||
| def detail_page_style(stops): | ||||
| @@ -866,57 +932,3 @@ def detail_page_style(stops): | ||||
|                                     border-radius: {border_radius_b}; | ||||
|     }} | ||||
| """ | ||||
|  | ||||
| class FileExplorerStyles: | ||||
|     WINDOW_STYLE = """ | ||||
|         QDialog { | ||||
|             background-color: #2d2d2d; | ||||
|             color: #ffffff; | ||||
|             font-family: "Arial"; | ||||
|             font-size: 14px; | ||||
|         } | ||||
|     """ | ||||
|  | ||||
|     PATH_LABEL_STYLE = """ | ||||
|         QLabel { | ||||
|             color: #3daee9; | ||||
|             font-size: 16px; | ||||
|             padding: 5px; | ||||
|         } | ||||
|     """ | ||||
|  | ||||
|     LIST_STYLE = """ | ||||
|         QListWidget { | ||||
|             font-size: 16px; | ||||
|             background-color: #353535; | ||||
|             color: #eee; | ||||
|             border: 1px solid #444; | ||||
|             border-radius: 4px; | ||||
|         } | ||||
|         QListWidget::item { | ||||
|             padding: 8px; | ||||
|             border-bottom: 1px solid #444; | ||||
|         } | ||||
|         QListWidget::item:selected { | ||||
|             background-color: #3daee9; | ||||
|             color: white; | ||||
|             border-radius: 2px; | ||||
|         } | ||||
|     """ | ||||
|  | ||||
|     BUTTON_STYLE = """ | ||||
|         QPushButton { | ||||
|             background-color: #3daee9; | ||||
|             color: white; | ||||
|             border: none; | ||||
|             padding: 8px 16px; | ||||
|             border-radius: 4px; | ||||
|             font-size: 14px; | ||||
|         } | ||||
|         QPushButton:hover { | ||||
|             background-color: #2c9fd8; | ||||
|         } | ||||
|         QPushButton:pressed { | ||||
|             background-color: #1a8fc7; | ||||
|         } | ||||
|     """ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user