4 Commits

Author SHA1 Message Date
14b5d6ce6f chore(changelog): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-02 10:45:12 +05:00
90e27a49ca chore(localization): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-02 10:44:23 +05:00
08c154ade6 feat(context_menu_manager): use EGS icon for import to Legendary action
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-02 10:42:37 +05:00
6efaff284f feat(context_menu_manager): add quick game stop via context menu
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-02 10:41:04 +05:00
11 changed files with 109 additions and 15 deletions

View File

@@ -10,7 +10,7 @@
- Начальная поддержка EGS (Без EOS, скачивания игр и запуска игр из сторонних магазинов) - Начальная поддержка EGS (Без EOS, скачивания игр и запуска игр из сторонних магазинов)
- Автодополнение bash для комманды portprotonqt - Автодополнение bash для комманды portprotonqt
- Поддержка геймпадов в диалоге выбора игры - Поддержка геймпадов в диалоге выбора игры
- Быстрый запуск игры через контекстное меню - Быстрый запуск и остановка игры через контекстное меню
- Иконки в контекстом меню - Иконки в контекстом меню
### Changed ### Changed

View File

@@ -20,9 +20,9 @@ Current translation status:
| Locale | Progress | Translated | | Locale | Progress | Translated |
| :----- | -------: | ---------: | | :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 183 | | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 185 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 183 | | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 185 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 183 of 183 | | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 185 of 185 |
--- ---

View File

@@ -20,9 +20,9 @@
| Локаль | Прогресс | Переведено | | Локаль | Прогресс | Переведено |
| :----- | -------: | ---------: | | :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 183 | | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 185 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 183 | | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 185 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 183 из 183 | | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 185 из 185 |
--- ---

View File

@@ -6,6 +6,8 @@ import subprocess
import threading import threading
import logging import logging
import orjson import orjson
import psutil
import signal
from PySide6.QtWidgets import QMessageBox, QDialog, QMenu from PySide6.QtWidgets import QMessageBox, QDialog, QMenu
from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt
from PySide6.QtGui import QDesktopServices, QIcon from PySide6.QtGui import QDesktopServices, QIcon
@@ -120,6 +122,35 @@ class ContextMenuManager:
logger.error("Failed to read installed.json: %s", e) logger.error("Failed to read installed.json: %s", e)
return False return False
def _is_game_running(self, game_card) -> bool:
"""
Check if the game associated with the game_card is currently running.
Args:
game_card: The GameCard instance containing game data.
Returns:
bool: True if the game is running, False otherwise.
"""
if game_card.game_source == "epic":
exe_path = get_egs_executable(game_card.appid, self.legendary_config_path)
if not exe_path or not os.path.exists(exe_path):
return False
current_exe = os.path.basename(exe_path)
else:
exec_line = self._get_exec_line(game_card.name, game_card.exec_line)
if not exec_line:
return False
exe_path = self._parse_exe_path(exec_line, game_card.name)
if not exe_path:
return False
current_exe = os.path.basename(exe_path)
# Check if the current_exe matches the target_exe in MainWindow
if hasattr(self.parent, 'target_exe') and self.parent.target_exe == current_exe:
return True
return False
def show_context_menu(self, game_card, pos: QPoint): def show_context_menu(self, game_card, pos: QPoint):
""" """
Show the context menu for a game card at the specified position. Show the context menu for a game card at the specified position.
@@ -140,7 +171,11 @@ class ContextMenuManager:
menu = QMenu(self.parent) menu = QMenu(self.parent)
menu.setStyleSheet(self.theme.CONTEXT_MENU_STYLE) menu.setStyleSheet(self.theme.CONTEXT_MENU_STYLE)
launch_action = menu.addAction(get_safe_icon("play"), _("Launch Game")) # Check if the game is running
is_running = self._is_game_running(game_card)
action_text = _("Stop Game") if is_running else _("Launch Game")
action_icon = "stop" if is_running else "play"
launch_action = menu.addAction(get_safe_icon(action_icon), action_text)
launch_action.triggered.connect( launch_action.triggered.connect(
lambda: self._launch_game(game_card) lambda: self._launch_game(game_card)
) )
@@ -153,7 +188,7 @@ class ContextMenuManager:
favorite_action.triggered.connect(lambda: self.toggle_favorite(game_card, not is_favorite)) favorite_action.triggered.connect(lambda: self.toggle_favorite(game_card, not is_favorite))
if game_card.game_source == "epic": if game_card.game_source == "epic":
import_action = menu.addAction(get_safe_icon("import"), _("Import to Legendary")) import_action = menu.addAction(get_safe_icon("epic_games"), _("Import to Legendary"))
import_action.triggered.connect( import_action.triggered.connect(
lambda: self.import_to_legendary(game_card.name, game_card.appid) lambda: self.import_to_legendary(game_card.name, game_card.appid)
) )
@@ -240,13 +275,44 @@ class ContextMenuManager:
def _launch_game(self, game_card): def _launch_game(self, game_card):
""" """
Launch a game using a validated exec_line, handling EGS games specifically. Launch or stop a game based on its current state.
Args: Args:
game_card: The GameCard instance containing game data. game_card: The GameCard instance containing game data.
""" """
if not self._check_portproton(): if not self._check_portproton():
return return
# Check if the game is running
if self._is_game_running(game_card):
# Stop the game
if hasattr(self.parent, 'game_processes') and self.parent.game_processes:
for proc in self.parent.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.parent.game_processes = []
self.parent.resetPlayButton()
if hasattr(self.parent, 'checkProcessTimer') and self.parent.checkProcessTimer is not None:
self.parent.checkProcessTimer.stop()
self.parent.checkProcessTimer.deleteLater()
self.parent.checkProcessTimer = None
self._show_status_message(_("Stopped '{game_name}'").format(game_name=game_card.name))
return
# Launch the game
if game_card.game_source == "epic": if game_card.game_source == "epic":
if not os.path.exists(self.legendary_path): if not os.path.exists(self.legendary_path):
self.signals.show_warning_dialog.emit( self.signals.show_warning_dialog.emit(

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-01 15:54+0500\n" "POT-Creation-Date: 2025-07-02 10:43+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de_DE\n" "Language: de_DE\n"
@@ -26,6 +26,9 @@ msgstr ""
msgid "PortProton is not found" msgid "PortProton is not found"
msgstr "" msgstr ""
msgid "Stop Game"
msgstr ""
msgid "Launch Game" msgid "Launch Game"
msgstr "" msgstr ""
@@ -65,6 +68,10 @@ msgstr ""
msgid "Delete from PortProton" msgid "Delete from PortProton"
msgstr "" msgstr ""
#, python-brace-format
msgid "Stopped '{game_name}'"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Legendary executable not found at {path}" msgid "Legendary executable not found at {path}"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-01 15:54+0500\n" "POT-Creation-Date: 2025-07-02 10:43+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: es_ES\n" "Language: es_ES\n"
@@ -26,6 +26,9 @@ msgstr ""
msgid "PortProton is not found" msgid "PortProton is not found"
msgstr "" msgstr ""
msgid "Stop Game"
msgstr ""
msgid "Launch Game" msgid "Launch Game"
msgstr "" msgstr ""
@@ -65,6 +68,10 @@ msgstr ""
msgid "Delete from PortProton" msgid "Delete from PortProton"
msgstr "" msgstr ""
#, python-brace-format
msgid "Stopped '{game_name}'"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Legendary executable not found at {path}" msgid "Legendary executable not found at {path}"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PortProtonQt 0.1.1\n" "Project-Id-Version: PortProtonQt 0.1.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-01 15:54+0500\n" "POT-Creation-Date: 2025-07-02 10:43+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -24,6 +24,9 @@ msgstr ""
msgid "PortProton is not found" msgid "PortProton is not found"
msgstr "" msgstr ""
msgid "Stop Game"
msgstr ""
msgid "Launch Game" msgid "Launch Game"
msgstr "" msgstr ""
@@ -63,6 +66,10 @@ msgstr ""
msgid "Delete from PortProton" msgid "Delete from PortProton"
msgstr "" msgstr ""
#, python-brace-format
msgid "Stopped '{game_name}'"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Legendary executable not found at {path}" msgid "Legendary executable not found at {path}"
msgstr "" msgstr ""

View File

@@ -9,8 +9,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-07-01 15:54+0500\n" "POT-Creation-Date: 2025-07-02 10:43+0500\n"
"PO-Revision-Date: 2025-07-01 15:54+0500\n" "PO-Revision-Date: 2025-07-02 10:43+0500\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: ru_RU\n" "Language: ru_RU\n"
"Language-Team: ru_RU <LL@li.org>\n" "Language-Team: ru_RU <LL@li.org>\n"
@@ -27,6 +27,9 @@ msgstr "Ошибка"
msgid "PortProton is not found" msgid "PortProton is not found"
msgstr "PortProton не найден" msgstr "PortProton не найден"
msgid "Stop Game"
msgstr "Остановить игру"
msgid "Launch Game" msgid "Launch Game"
msgstr "Запустить игру" msgstr "Запустить игру"
@@ -66,6 +69,10 @@ msgstr "Редактировать"
msgid "Delete from PortProton" msgid "Delete from PortProton"
msgstr "Удалить из PortProton" msgstr "Удалить из PortProton"
#, python-brace-format
msgid "Stopped '{game_name}'"
msgstr "Остановлен(а) '{game_name}'"
#, python-brace-format #, python-brace-format
msgid "Legendary executable not found at {path}" msgid "Legendary executable not found at {path}"
msgstr "Legendary не найден по пути {path}" msgstr "Legendary не найден по пути {path}"