forked from Boria138/PortProtonQt
feat: use GameCard on autonstall tab
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -1022,6 +1022,130 @@ class InputManager(QObject):
|
||||
elif current_row_idx == 0:
|
||||
self._parent.tabButtons[0].setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
|
||||
# Autoinstall tab navigation(index 1)
|
||||
if self._parent.stackedWidget.currentIndex() == 1 and code in (ecodes.ABS_HAT0X, ecodes.ABS_HAT0Y):
|
||||
focused = QApplication.focusWidget()
|
||||
game_cards = self._parent.autoInstallContainer.findChildren(GameCard)
|
||||
if not game_cards:
|
||||
return
|
||||
|
||||
scroll_area = self._parent.autoInstallContainer.parentWidget()
|
||||
while scroll_area and not isinstance(scroll_area, QScrollArea):
|
||||
scroll_area = scroll_area.parentWidget()
|
||||
|
||||
# If no focused widget or not a GameCard, focus the first card
|
||||
if not isinstance(focused, GameCard) or focused not in game_cards:
|
||||
game_cards[0].setFocus()
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(game_cards[0], 50, 50)
|
||||
return
|
||||
|
||||
cards = self._parent.autoInstallContainer.findChildren(GameCard, options=Qt.FindChildOption.FindChildrenRecursively)
|
||||
if not cards:
|
||||
return
|
||||
# Group cards by rows with tolerance for y-position
|
||||
rows = {}
|
||||
y_tolerance = 10 # Allow slight variations in y-position
|
||||
for card in cards:
|
||||
y = card.pos().y()
|
||||
matched = False
|
||||
for row_y in rows:
|
||||
if abs(y - row_y) <= y_tolerance:
|
||||
rows[row_y].append(card)
|
||||
matched = True
|
||||
break
|
||||
if not matched:
|
||||
rows[y] = [card]
|
||||
sorted_rows = sorted(rows.items(), key=lambda x: x[0])
|
||||
if not sorted_rows:
|
||||
return
|
||||
current_row_idx = None
|
||||
current_col_idx = None
|
||||
for row_idx, (_y, row_cards) in enumerate(sorted_rows):
|
||||
for idx, card in enumerate(row_cards):
|
||||
if card == focused:
|
||||
current_row_idx = row_idx
|
||||
current_col_idx = idx
|
||||
break
|
||||
if current_row_idx is not None:
|
||||
break
|
||||
|
||||
# Fallback: if focused card not found, select closest row by y-position
|
||||
if current_row_idx is None:
|
||||
if not sorted_rows: # Additional safety check
|
||||
return
|
||||
focused_y = focused.pos().y()
|
||||
current_row_idx = min(range(len(sorted_rows)), key=lambda i: abs(sorted_rows[i][0] - focused_y))
|
||||
if current_row_idx >= len(sorted_rows): # Safety check
|
||||
return
|
||||
current_row = sorted_rows[current_row_idx][1]
|
||||
focused_x = focused.pos().x() + focused.width() / 2
|
||||
current_col_idx = min(range(len(current_row)), key=lambda i: abs((current_row[i].pos().x() + current_row[i].width() / 2) - focused_x), default=0) # type: ignore
|
||||
|
||||
# Add null checks before using current_row_idx and current_col_idx
|
||||
if current_row_idx is None or current_col_idx is None or current_row_idx >= len(sorted_rows):
|
||||
return
|
||||
|
||||
current_row = sorted_rows[current_row_idx][1]
|
||||
if code == ecodes.ABS_HAT0X and value != 0:
|
||||
if value < 0: # Left
|
||||
if current_col_idx > 0:
|
||||
next_card = current_row[current_col_idx - 1]
|
||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||
else:
|
||||
if current_row_idx > 0:
|
||||
prev_row = sorted_rows[current_row_idx - 1][1]
|
||||
next_card = prev_row[-1] if prev_row else None
|
||||
if next_card:
|
||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||
elif value > 0: # Right
|
||||
if current_col_idx < len(current_row) - 1:
|
||||
next_card = current_row[current_col_idx + 1]
|
||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||
else:
|
||||
if current_row_idx < len(sorted_rows) - 1:
|
||||
next_row = sorted_rows[current_row_idx + 1][1]
|
||||
next_card = next_row[0] if next_row else None
|
||||
if next_card:
|
||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||
elif code == ecodes.ABS_HAT0Y and value != 0:
|
||||
if value > 0: # Down
|
||||
if current_row_idx < len(sorted_rows) - 1:
|
||||
next_row = sorted_rows[current_row_idx + 1][1]
|
||||
current_x = focused.pos().x() + focused.width() / 2
|
||||
next_card = min(
|
||||
next_row,
|
||||
key=lambda c: abs((c.pos().x() + c.width() / 2) - current_x),
|
||||
default=None
|
||||
)
|
||||
if next_card:
|
||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||
elif value < 0: # Up
|
||||
if current_row_idx > 0:
|
||||
prev_row = sorted_rows[current_row_idx - 1][1]
|
||||
current_x = focused.pos().x() + focused.width() / 2
|
||||
next_card = min(
|
||||
prev_row,
|
||||
key=lambda c: abs((c.pos().x() + c.width() / 2) - current_x),
|
||||
default=None
|
||||
)
|
||||
if next_card:
|
||||
next_card.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
if scroll_area:
|
||||
scroll_area.ensureWidgetVisible(next_card, 50, 50)
|
||||
elif current_row_idx == 0:
|
||||
self._parent.tabButtons[0].setFocus(Qt.FocusReason.OtherFocusReason)
|
||||
|
||||
# Vertical navigation in other tabs
|
||||
elif code == ecodes.ABS_HAT0Y and value != 0:
|
||||
focused = QApplication.focusWidget()
|
||||
@@ -1088,6 +1212,21 @@ class InputManager(QObject):
|
||||
keyboard.on_backspace_pressed()
|
||||
elif value == 0:
|
||||
keyboard.stop_backspace_repeat()
|
||||
elif button_code in BUTTONS['search']: # Кнопка Y/Square - поиск (tab-specific)
|
||||
if value == 1:
|
||||
current_index = self._parent.stackedWidget.currentIndex()
|
||||
search_field = None
|
||||
if current_index == 0: # Library tab
|
||||
search_field = self._parent.searchLineEdit
|
||||
elif current_index == 1: # Auto Install tab
|
||||
search_field = self._parent.autoInstallSearchLineEdit
|
||||
else:
|
||||
return
|
||||
|
||||
if search_field:
|
||||
search_field.setFocus()
|
||||
keyboard.current_input_widget = search_field
|
||||
keyboard.show()
|
||||
|
||||
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
||||
app = QApplication.instance()
|
||||
|
@@ -11,7 +11,7 @@ from portprotonqt.logger import get_logger
|
||||
from portprotonqt.dialogs import AddGameDialog, FileExplorer, WinetricksDialog
|
||||
from portprotonqt.game_card import GameCard
|
||||
from portprotonqt.animations import DetailPageAnimations
|
||||
from portprotonqt.custom_widgets import ClickableLabel, AutoSizeButton, NavLabel
|
||||
from portprotonqt.custom_widgets import ClickableLabel, AutoSizeButton, NavLabel, FlowLayout
|
||||
from portprotonqt.portproton_api import PortProtonAPI
|
||||
from portprotonqt.input_manager import InputManager
|
||||
from portprotonqt.context_menu_manager import ContextMenuManager, CustomLineEdit
|
||||
@@ -518,8 +518,6 @@ class MainWindow(QMainWindow):
|
||||
self.progress_bar.setValue(100)
|
||||
if exit_code == 0:
|
||||
self.update_status_message.emit(_("Installation completed successfully."), 5000)
|
||||
# Reload library after delay
|
||||
QTimer.singleShot(3000, self.loadGames)
|
||||
else:
|
||||
self.update_status_message.emit(_("Installation failed."), 5000)
|
||||
QMessageBox.warning(self, _("Error"), f"Installation failed (code: {exit_code}).")
|
||||
@@ -1061,113 +1059,150 @@ class MainWindow(QMainWindow):
|
||||
get_steam_game_info_async(final_name, exec_line, on_steam_info)
|
||||
|
||||
def createAutoInstallTab(self):
|
||||
"""Create the Auto Install tab with flow layout of simple game cards (cover, name, install button)."""
|
||||
from portprotonqt.localization import _
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
layout.setContentsMargins(20, 20, 20, 20)
|
||||
layout.setSpacing(20)
|
||||
|
||||
# Header label
|
||||
header = QLabel(_("Auto Install Games"))
|
||||
header.setStyleSheet(self.theme.DETAIL_PAGE_TITLE_STYLE)
|
||||
layout.addWidget(header)
|
||||
autoInstallPage = QWidget()
|
||||
autoInstallPage.setStyleSheet(self.theme.MAIN_WINDOW_STYLE)
|
||||
autoInstallLayout = QVBoxLayout(autoInstallPage)
|
||||
autoInstallLayout.setContentsMargins(0, 0, 0, 0)
|
||||
autoInstallLayout.setSpacing(0)
|
||||
|
||||
# Scroll area for games
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||
scroll_widget = QWidget()
|
||||
from portprotonqt.custom_widgets import FlowLayout
|
||||
self.auto_install_flow_layout = FlowLayout(scroll_widget) # Store reference for potential updates
|
||||
self.auto_install_flow_layout.setSpacing(15)
|
||||
self.auto_install_flow_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# Верхняя панель с заголовком и поиском
|
||||
headerWidget = QWidget()
|
||||
headerLayout = QHBoxLayout(headerWidget)
|
||||
headerLayout.setContentsMargins(20, 10, 20, 10)
|
||||
headerLayout.setSpacing(10)
|
||||
|
||||
# Load games asynchronously (though now sync inside, but callback for consistency)
|
||||
# Заголовок
|
||||
titleLabel = QLabel(_("Auto Install Games"))
|
||||
titleLabel.setStyleSheet(self.theme.TAB_TITLE_STYLE)
|
||||
titleLabel.setAlignment(Qt.AlignVCenter | Qt.AlignLeft)
|
||||
headerLayout.addWidget(titleLabel)
|
||||
|
||||
headerLayout.addStretch()
|
||||
|
||||
# Поисковая строка
|
||||
self.autoInstallSearchLineEdit = CustomLineEdit(self, theme=self.theme)
|
||||
icon: QIcon = cast(QIcon, self.theme_manager.get_icon("search"))
|
||||
action_pos = cast(int, CustomLineEdit.ActionPosition.LeadingPosition)
|
||||
self.search_action = self.autoInstallSearchLineEdit.addAction(icon, action_pos)
|
||||
self.autoInstallSearchLineEdit.setMaximumWidth(200)
|
||||
self.autoInstallSearchLineEdit.setPlaceholderText(_("Find Games ..."))
|
||||
self.autoInstallSearchLineEdit.setClearButtonEnabled(True)
|
||||
self.autoInstallSearchLineEdit.setStyleSheet(self.theme.SEARCH_EDIT_STYLE)
|
||||
self.autoInstallSearchLineEdit.textChanged.connect(self.filterAutoInstallGames)
|
||||
headerLayout.addWidget(self.autoInstallSearchLineEdit)
|
||||
|
||||
autoInstallLayout.addWidget(headerWidget)
|
||||
|
||||
# Прогресс-бар
|
||||
self.autoInstallProgress = QProgressBar()
|
||||
self.autoInstallProgress.setStyleSheet(self.theme.PROGRESS_BAR_STYLE)
|
||||
self.autoInstallProgress.setVisible(False)
|
||||
autoInstallLayout.addWidget(self.autoInstallProgress)
|
||||
|
||||
# Скролл
|
||||
self.autoInstallScrollArea = QScrollArea()
|
||||
self.autoInstallScrollArea.setWidgetResizable(True)
|
||||
self.autoInstallScrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.autoInstallScrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.autoInstallScrollArea.setStyleSheet("QScrollArea { border: none; background: transparent; }")
|
||||
|
||||
self.autoInstallContainer = QWidget()
|
||||
self.autoInstallContainerLayout = FlowLayout(self.autoInstallContainer)
|
||||
self.autoInstallContainer.setLayout(self.autoInstallContainerLayout)
|
||||
self.autoInstallScrollArea.setWidget(self.autoInstallContainer)
|
||||
|
||||
autoInstallLayout.addWidget(self.autoInstallScrollArea)
|
||||
|
||||
# Хранение карточек
|
||||
self.autoInstallGameCards = {}
|
||||
self.allAutoInstallCards = []
|
||||
|
||||
# Обновление обложки
|
||||
def on_autoinstall_cover_updated(exe_name, local_path):
|
||||
if exe_name in self.autoInstallGameCards and local_path:
|
||||
card = self.autoInstallGameCards[exe_name]
|
||||
card.cover_path = local_path
|
||||
load_pixmap_async(local_path, self.card_width, int(self.card_width * 1.5), card.on_cover_loaded)
|
||||
|
||||
# Загрузка игр
|
||||
def on_autoinstall_games_loaded(games: list[tuple]):
|
||||
# Clear existing widgets
|
||||
while self.auto_install_flow_layout.count():
|
||||
child = self.auto_install_flow_layout.takeAt(0)
|
||||
if child.widget():
|
||||
self.autoInstallProgress.setVisible(False)
|
||||
|
||||
# Очистка
|
||||
while self.autoInstallContainerLayout.count():
|
||||
child = self.autoInstallContainerLayout.takeAt(0)
|
||||
if child:
|
||||
child.widget().deleteLater()
|
||||
|
||||
for game in games:
|
||||
name = game[0]
|
||||
description = game[1]
|
||||
cover_path = game[2]
|
||||
exec_line = game[4]
|
||||
script_name = exec_line.split("autoinstall:")[1] if exec_line.startswith("autoinstall:") else ""
|
||||
self.autoInstallGameCards.clear()
|
||||
self.allAutoInstallCards.clear()
|
||||
|
||||
# Create simple card frame
|
||||
card_frame = QFrame()
|
||||
card_frame.setFixedWidth(self.card_width)
|
||||
card_frame.setStyleSheet(self.theme.GAME_CARD_STYLE if hasattr(self.theme, 'GAME_CARD_STYLE') else "")
|
||||
card_layout = QVBoxLayout(card_frame)
|
||||
card_layout.setContentsMargins(10, 10, 10, 10)
|
||||
card_layout.setSpacing(10)
|
||||
if not games:
|
||||
return
|
||||
|
||||
# Cover image
|
||||
cover_label = QLabel()
|
||||
cover_label.setFixedHeight(120)
|
||||
cover_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
pixmap = QPixmap()
|
||||
if cover_path and os.path.exists(cover_path) and pixmap.load(cover_path):
|
||||
scaled_pix = pixmap.scaled(self.card_width - 40, 120, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
||||
cover_label.setPixmap(scaled_pix)
|
||||
else:
|
||||
# Placeholder
|
||||
placeholder_icon = self.theme_manager.get_theme_image("placeholder", self.current_theme_name)
|
||||
if placeholder_icon:
|
||||
pixmap.load(str(placeholder_icon))
|
||||
scaled_pix = pixmap.scaled(100, 100, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)
|
||||
cover_label.setPixmap(scaled_pix)
|
||||
card_layout.addWidget(cover_label)
|
||||
# Callback для запуска установки
|
||||
def select_callback(name, description, cover_path, appid, exec_line, controller_support, *_):
|
||||
if not exec_line or not exec_line.startswith("autoinstall:"):
|
||||
logger.warning(f"Invalid exec_line for autoinstall: {exec_line}")
|
||||
return
|
||||
script_name = exec_line[11:].lstrip(':').strip()
|
||||
self.launch_autoinstall(script_name)
|
||||
|
||||
# Name label
|
||||
name_label = QLabel(name)
|
||||
name_label.setWordWrap(True)
|
||||
name_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
name_label.setStyleSheet(self.theme.CARD_TITLE_STYLE if hasattr(self.theme, 'CARD_TITLE_STYLE') else "")
|
||||
card_layout.addWidget(name_label)
|
||||
# Создаём карточки
|
||||
for game_tuple in games:
|
||||
name, description, cover_path, appid, controller_support, exec_line, *_ , game_source, exe_name = game_tuple
|
||||
|
||||
# Optional short description
|
||||
if description:
|
||||
desc_label = QLabel(description[:100] + "..." if len(description) > 100 else description)
|
||||
desc_label.setWordWrap(True)
|
||||
desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
desc_label.setStyleSheet(self.theme.CARD_DESC_STYLE if hasattr(self.theme, 'CARD_DESC_STYLE') else "")
|
||||
card_layout.addWidget(desc_label)
|
||||
card = GameCard(
|
||||
name, description, cover_path, appid, controller_support,
|
||||
exec_line, None, None, None,
|
||||
None, None, None, game_source,
|
||||
select_callback=select_callback,
|
||||
theme=self.theme,
|
||||
card_width=self.card_width,
|
||||
parent=self.autoInstallContainer,
|
||||
)
|
||||
|
||||
# Install button
|
||||
install_btn = AutoSizeButton(_("Install"))
|
||||
install_btn.setStyleSheet(self.theme.PLAY_BUTTON_STYLE)
|
||||
install_btn.clicked.connect(lambda checked, s=script_name: self.launch_autoinstall(s))
|
||||
card_layout.addWidget(install_btn)
|
||||
self.autoInstallGameCards[exe_name] = card
|
||||
self.allAutoInstallCards.append(card)
|
||||
self.autoInstallContainerLayout.addWidget(card)
|
||||
|
||||
card_layout.addStretch()
|
||||
# Загружаем недостающие обложки
|
||||
for game_tuple in games:
|
||||
name, _, cover_path, *_ , game_source, exe_name = game_tuple
|
||||
if not cover_path:
|
||||
self.portproton_api.download_autoinstall_cover_async(
|
||||
exe_name, timeout=5,
|
||||
callback=lambda path, ex=exe_name: on_autoinstall_cover_updated(ex, path)
|
||||
)
|
||||
|
||||
# Add to flow layout
|
||||
self.auto_install_flow_layout.addWidget(card_frame)
|
||||
self.autoInstallContainer.updateGeometry()
|
||||
self.autoInstallScrollArea.updateGeometry()
|
||||
self.filterAutoInstallGames()
|
||||
|
||||
scroll.setWidget(scroll_widget)
|
||||
layout.addWidget(scroll)
|
||||
|
||||
# Trigger load
|
||||
# Показываем прогресс
|
||||
self.autoInstallProgress.setVisible(True)
|
||||
self.autoInstallProgress.setRange(0, 0)
|
||||
self.portproton_api.get_autoinstall_games_async(on_autoinstall_games_loaded)
|
||||
|
||||
self.stackedWidget.addWidget(tab)
|
||||
self.stackedWidget.addWidget(autoInstallPage)
|
||||
|
||||
def filterAutoInstallGames(self):
|
||||
"""Filter auto install game cards based on search text."""
|
||||
search_text = self.autoInstallSearchLineEdit.text().lower().strip()
|
||||
visible_count = 0
|
||||
|
||||
def on_auto_install_search_changed(self, text: str):
|
||||
"""Filter auto-install games based on search text."""
|
||||
filtered_games = [g for g in self.auto_install_games if text.lower() in g[0].lower() or text.lower() in g[1].lower()]
|
||||
self.populate_auto_install_grid(filtered_games)
|
||||
self.auto_install_clear_search_button.setVisible(bool(text))
|
||||
for card in self.allAutoInstallCards:
|
||||
if search_text in card.name.lower():
|
||||
card.setVisible(True)
|
||||
visible_count += 1
|
||||
else:
|
||||
card.setVisible(False)
|
||||
|
||||
def clear_auto_install_search(self):
|
||||
"""Clear the auto-install search and repopulate grid."""
|
||||
self.auto_install_search_line.clear()
|
||||
self.populate_auto_install_grid(self.auto_install_games)
|
||||
# Re-layout the container
|
||||
self.autoInstallContainerLayout.invalidate()
|
||||
self.autoInstallContainer.updateGeometry()
|
||||
self.autoInstallScrollArea.updateGeometry()
|
||||
|
||||
def createWineTab(self):
|
||||
"""Вкладка 'Wine Settings'."""
|
||||
@@ -1787,7 +1822,7 @@ class MainWindow(QMainWindow):
|
||||
# # 8. Legendary Authentication
|
||||
# self.legendaryAuthButton = AutoSizeButton(
|
||||
# _("Open Legendary Login"),
|
||||
# icon=self.theme_manager.get_icon("login")
|
||||
# icon=self.theme_manager.get_icon("login")self.theme_manager.get_icon("login")
|
||||
# )
|
||||
# self.legendaryAuthButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
|
||||
# self.legendaryAuthButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
@@ -2713,11 +2748,6 @@ class MainWindow(QMainWindow):
|
||||
QDesktopServices.openUrl(url)
|
||||
return
|
||||
|
||||
if exec_line.startswith("autoinstall:"):
|
||||
script_name = exec_line.split("autoinstall:")[1]
|
||||
self.launch_autoinstall(script_name)
|
||||
return
|
||||
|
||||
# Обработка EGS-игр
|
||||
if exec_line.startswith("legendary:launch:"):
|
||||
app_name = exec_line.split("legendary:launch:")[1]
|
||||
|
@@ -136,42 +136,38 @@ class PortProtonAPI:
|
||||
callback(results)
|
||||
|
||||
def download_autoinstall_cover_async(self, exe_name: str, timeout: int = 5, callback: Callable[[str | None], None] | None = None) -> None:
|
||||
"""Download only autoinstall cover image (no metadata)."""
|
||||
"""Download only autoinstall cover image (PNG only, no metadata)."""
|
||||
xdg_data_home = os.getenv("XDG_DATA_HOME",
|
||||
os.path.join(os.path.expanduser("~"), ".local", "share"))
|
||||
user_game_folder = os.path.join(xdg_data_home, "PortProtonQt", "custom_data", "autoinstall", exe_name)
|
||||
os.makedirs(user_game_folder, exist_ok=True)
|
||||
autoinstall_root = os.path.join(xdg_data_home, "PortProtonQt", "custom_data", "autoinstall")
|
||||
user_game_folder = os.path.join(autoinstall_root, exe_name)
|
||||
|
||||
cover_extensions = [".png", ".jpg", ".jpeg", ".bmp"]
|
||||
results: str | None = None
|
||||
pending_downloads = 0
|
||||
if not os.path.isdir(user_game_folder):
|
||||
try:
|
||||
os.mkdir(user_game_folder)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
def on_cover_downloaded(local_path: str | None, ext: str):
|
||||
nonlocal pending_downloads, results
|
||||
cover_url = f"{self.base_url}/{exe_name}/cover.png"
|
||||
local_cover_path = os.path.join(user_game_folder, "cover.png")
|
||||
|
||||
def on_cover_downloaded(local_path: str | None):
|
||||
if local_path:
|
||||
logger.info(f"Async autoinstall cover downloaded for {exe_name}: {local_path}")
|
||||
results = local_path
|
||||
else:
|
||||
logger.debug(f"No autoinstall cover downloaded for {exe_name} with extension {ext}")
|
||||
pending_downloads -= 1
|
||||
if pending_downloads == 0 and callback:
|
||||
callback(results)
|
||||
logger.debug(f"No autoinstall cover downloaded for {exe_name}")
|
||||
if callback:
|
||||
callback(local_path)
|
||||
|
||||
for ext in cover_extensions:
|
||||
cover_url = f"{self.base_url}/{exe_name}/cover{ext}"
|
||||
if self._check_file_exists(cover_url, timeout):
|
||||
local_cover_path = os.path.join(user_game_folder, f"cover{ext}")
|
||||
pending_downloads += 1
|
||||
self.downloader.download_async(
|
||||
cover_url,
|
||||
local_cover_path,
|
||||
timeout=timeout,
|
||||
callback=lambda path, ext=ext: on_cover_downloaded(path, ext)
|
||||
callback=on_cover_downloaded
|
||||
)
|
||||
break
|
||||
|
||||
if pending_downloads == 0:
|
||||
logger.debug(f"No autoinstall covers found for {exe_name}")
|
||||
else:
|
||||
logger.debug(f"No autoinstall cover found for {exe_name}")
|
||||
if callback:
|
||||
callback(None)
|
||||
|
||||
@@ -212,10 +208,8 @@ class PortProtonAPI:
|
||||
portwine_match = None
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
# Игнорируем закомментированные строки
|
||||
if stripped.startswith("#"):
|
||||
continue
|
||||
# Ищем portwine_exe только в активных строках
|
||||
if "portwine_exe" in stripped and "=" in stripped:
|
||||
portwine_match = stripped
|
||||
break
|
||||
@@ -238,7 +232,7 @@ class PortProtonAPI:
|
||||
return None, None
|
||||
|
||||
def get_autoinstall_games_async(self, callback: Callable[[list[tuple]], None]) -> None:
|
||||
"""Load auto-install games with user/builtin covers and async autoinstall cover download."""
|
||||
"""Load auto-install games with user/builtin covers (no async download here)."""
|
||||
games = []
|
||||
auto_dir = os.path.join(self.portproton_location, "data", "scripts", "pw_autoinstall")
|
||||
if not os.path.exists(auto_dir):
|
||||
@@ -275,15 +269,10 @@ class PortProtonAPI:
|
||||
cover_path = os.path.join(user_game_folder, candidate)
|
||||
break
|
||||
|
||||
# Если обложки нет — пытаемся скачать
|
||||
if not cover_path:
|
||||
logger.debug(f"No local cover found for autoinstall {exe_name}, trying to download...")
|
||||
def on_cover_downloaded(path):
|
||||
if path:
|
||||
logger.info(f"Downloaded autoinstall cover for {exe_name}: {path}")
|
||||
self.download_autoinstall_cover_async(exe_name, timeout=5, callback=on_cover_downloaded)
|
||||
logger.debug(f"No local cover found for autoinstall {exe_name}")
|
||||
|
||||
# Формируем кортеж игры
|
||||
# Формируем кортеж игры (добавлен exe_name в конец)
|
||||
game_tuple = (
|
||||
display_name, # name
|
||||
"", # description
|
||||
@@ -297,7 +286,8 @@ class PortProtonAPI:
|
||||
"", # anticheat_status
|
||||
0, # last_played
|
||||
0, # playtime_seconds
|
||||
"autoinstall" # game_source
|
||||
"autoinstall", # game_source
|
||||
exe_name # exe_name
|
||||
)
|
||||
games.append(game_tuple)
|
||||
|
||||
|
Reference in New Issue
Block a user