Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
3aa6b90f1e | |||
66f19afdaa | |||
0a915b4630 | |||
762d071396 | |||
ca442f3318 | |||
9708b8d1a2 | |||
0802c97d9f | |||
c59a0401b1 | |||
3a8a9bf2eb | |||
73afe7c554 | |||
a8ec615352 | |||
74622356fc | |||
be10225e4e | |||
329b7f1038
|
|||
fea07e19fe
|
|||
37b108f689
|
|||
78f5118709 | |||
1f14dd7fdf | |||
3d3bdd8f98 | |||
9d7c674544 | |||
e6c90508ab | |||
d0eea92139
|
|||
04726491c0
|
|||
bd1b7c07ae
|
|||
e6161d2e3f
|
|||
b82080600f
|
|||
05693514aa
|
|||
1c2835a933
|
|||
d229914fb6
|
|||
ce69a18249
|
|||
4d58830910 | |||
016ba537be
|
|||
6eeb93f6ba
|
|||
3f5d058740
|
|||
1a9228b76d
|
|||
e9e0bea854
|
|||
f7d9f5c150
|
|||
bcb5987d31
|
|||
b1aa987e4e
|
|||
f4c8b70bd0
|
|||
ff960df77c
|
|||
a57f509295
|
|||
32bbe89911
|
|||
593db00166
|
|||
79a78c785b
|
|||
0b92d058a9
|
|||
9df22edfc9
|
|||
4559231712
|
|||
18dbd42369
|
|||
76c0e607c5
|
|||
a91c9dacd8
|
@ -40,7 +40,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
fedora_version: [40, 41, 42, rawhide]
|
fedora_version: [41, 42, rawhide]
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: fedora:${{ matrix.fedora_version }}
|
image: fedora:${{ matrix.fedora_version }}
|
||||||
|
@ -97,7 +97,7 @@ jobs:
|
|||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
fedora_version: [40, 41, 42, rawhide]
|
fedora_version: [41, 42, rawhide]
|
||||||
|
|
||||||
container:
|
container:
|
||||||
image: fedora:${{ matrix.fedora_version }}
|
image: fedora:${{ matrix.fedora_version }}
|
||||||
|
30
CHANGELOG.md
30
CHANGELOG.md
@ -3,6 +3,27 @@
|
|||||||
Все заметные изменения в этом проекте фиксируются в этом файле.
|
Все заметные изменения в этом проекте фиксируются в этом файле.
|
||||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
|
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Аргумент `--session` для запуска приложения в gamescope с GAMESCOPE_CMD
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Удалены сборки для Fedora 40
|
||||||
|
- Перенесены параметры анимации GameCard в `styles.py` с подробной документацией для поддержки кастомизации тем.
|
||||||
|
- Статус выделения и наведения на карточки теперь взаимоисключают друг друга
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Дублирование обводки выделения карточек при быстром перемешении мыши
|
||||||
|
- Завершение приложения при закритие окна
|
||||||
|
- Использование системной палитры в темах
|
||||||
|
- Ошибки темы в нативном пакете
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
- @Dervart
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [0.1.2] - 2025-06-15
|
## [0.1.2] - 2025-06-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@ -64,6 +85,10 @@
|
|||||||
- Корректная обработка событий геймпада во время игры
|
- Корректная обработка событий геймпада во время игры
|
||||||
- Убийсво всех процессов "зомби" при закрытии программы
|
- Убийсво всех процессов "зомби" при закрытии программы
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
- @Vector_null
|
||||||
|
- @Dervart
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## [0.1.1] – 2025-05-17
|
## [0.1.1] – 2025-05-17
|
||||||
@ -84,6 +109,11 @@
|
|||||||
- Зависание GUI
|
- Зависание GUI
|
||||||
- Сбой при повреждённом Steam
|
- Сбой при повреждённом Steam
|
||||||
|
|
||||||
|
### Contributors
|
||||||
|
- @Vector_null
|
||||||
|
- @Dervart
|
||||||
|
- @alex2844
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
> См. подробности по каждому коммиту в истории репозитория.
|
> См. подробности по каждому коммиту в истории репозитория.
|
||||||
|
12
README.md
12
README.md
@ -4,7 +4,6 @@
|
|||||||
<p align="center">Удобный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store. Оно объединяет библиотеки игр в единый центр для лёгкой навигации и организации. Лёгкая структура и кроссплатформенная поддержка обеспечивают цельный игровой опыт без необходимости использования нескольких лаунчеров. Интеграция с PortProton упрощает запуск Windows-игр на Linux с минимальной настройкой.</p>
|
<p align="center">Удобный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store. Оно объединяет библиотеки игр в единый центр для лёгкой навигации и организации. Лёгкая структура и кроссплатформенная поддержка обеспечивают цельный игровой опыт без необходимости использования нескольких лаунчеров. Интеграция с PortProton упрощает запуск Windows-игр на Linux с минимальной настройкой.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## В планах
|
## В планах
|
||||||
|
|
||||||
- [X] Адаптировать структуру проекта для поддержки инструментов сборки
|
- [X] Адаптировать структуру проекта для поддержки инструментов сборки
|
||||||
@ -15,7 +14,8 @@
|
|||||||
- [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
|
- [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
|
||||||
- [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор)
|
- [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор)
|
||||||
- [ ] Продумать систему вкладок вместо текущей
|
- [ ] Продумать систему вкладок вместо текущей
|
||||||
- [X] [Добавить сессию Gamescope, аналогичную той, что используется в SteamOS](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
|
- [ ] [Добавить сессию Gamescope, аналогичную той, что используется в SteamOS](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
|
||||||
|
- [X] Разобраться почему теряется часть стилей в Gamescope
|
||||||
- [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800)
|
- [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800)
|
||||||
- [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
|
- [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
|
||||||
- [X] Получать описания и названия игр из базы данных Steam
|
- [X] Получать описания и названия игр из базы данных Steam
|
||||||
@ -39,9 +39,11 @@
|
|||||||
- [X] Добавить в карточки данные с AreWeAntiCheatYet
|
- [X] Добавить в карточки данные с AreWeAntiCheatYet
|
||||||
- [X] Продублировать бейджи с карточки на страницу с деталями игры
|
- [X] Продублировать бейджи с карточки на страницу с деталями игры
|
||||||
- [X] Добавить парсинг ярлыков из Steam
|
- [X] Добавить парсинг ярлыков из Steam
|
||||||
- [X] Добавить парсинг ярлыков из EGS (скрыто для переработки)
|
- [X] Добавить парсинг ярлыков из EGS
|
||||||
- [ ] Избавиться от бинарника legendary
|
- [ ] Избавиться от бинарника legendary
|
||||||
- [ ] Добавить запуск и скачивание игр из EGS
|
- [X] Добавить запуск игр из EGS
|
||||||
|
- [ ] Добавить скачивание игр из EGS
|
||||||
|
- [ ] Добавить поддержку запуска сторонних игр из EGS
|
||||||
- [ ] Добавить авторизацию в EGS через WebView вместо ручного ввода
|
- [ ] Добавить авторизацию в EGS через WebView вместо ручного ввода
|
||||||
- [X] Получать описания для игр из EGS через их [API](https://store-content.ak.epicgames.com/api)
|
- [X] Получать описания для игр из EGS через их [API](https://store-content.ak.epicgames.com/api)
|
||||||
- [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql)
|
- [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql)
|
||||||
@ -55,6 +57,7 @@
|
|||||||
- [X] Добавить систему избранного для карточек
|
- [X] Добавить систему избранного для карточек
|
||||||
- [X] Заменить все `print` на `logging`
|
- [X] Заменить все `print` на `logging`
|
||||||
- [ ] Привести все логи к единому языку
|
- [ ] Привести все логи к единому языку
|
||||||
|
- [ ] Уменьшить количество строк для перевода
|
||||||
- [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog)
|
- [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog)
|
||||||
- [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py`
|
- [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py`
|
||||||
- [X] Исправить частичное применение тем на лету
|
- [X] Исправить частичное применение тем на лету
|
||||||
@ -68,6 +71,7 @@
|
|||||||
- [X] Скопировать логику управления с D-pad на стрелки с клавиатуры
|
- [X] Скопировать логику управления с D-pad на стрелки с клавиатуры
|
||||||
- [ ] Доделать светлую тему
|
- [ ] Доделать светлую тему
|
||||||
- [ ] Добавить подсказки к управлению с геймпада
|
- [ ] Добавить подсказки к управлению с геймпада
|
||||||
|
- [ ] Добавить загрузку звуков в темы например для добавления звука запуска в тему и тд
|
||||||
|
|
||||||
### Установка (devel)
|
### Установка (devel)
|
||||||
|
|
||||||
|
@ -28,19 +28,19 @@ BuildRequires: git
|
|||||||
%package -n python3-%{pypi_name}-git
|
%package -n python3-%{pypi_name}-git
|
||||||
Summary: %{summary}
|
Summary: %{summary}
|
||||||
%{?python_provide:%python_provide python3-%{pypi_name}}
|
%{?python_provide:%python_provide python3-%{pypi_name}}
|
||||||
Requires: python3dist(babel)
|
Requires: python3-babel
|
||||||
Requires: python3dist(evdev)
|
Requires: python3-evdev
|
||||||
Requires: python3dist(icoextract)
|
Requires: python3-icoextract
|
||||||
Requires: python3dist(numpy)
|
Requires: python3-numpy
|
||||||
Requires: python3dist(orjson)
|
Requires: python3-orjson
|
||||||
Requires: python3dist(psutil)
|
Requires: python3-psutil
|
||||||
Requires: python3dist(pyside6)
|
Requires: python3-pyside6
|
||||||
Requires: python3dist(pyudev)
|
Requires: python3-pyudev
|
||||||
Requires: python3dist(requests)
|
Requires: python3-requests
|
||||||
Requires: python3dist(tqdm)
|
Requires: python3-tqdm
|
||||||
Requires: python3dist(vdf)
|
Requires: python3-vdf
|
||||||
Requires: python3dist(pefile)
|
Requires: python3-pefile
|
||||||
Requires: python3dist(pillow)
|
Requires: python3-pillow
|
||||||
Requires: perl-Image-ExifTool
|
Requires: perl-Image-ExifTool
|
||||||
Requires: xdg-utils
|
Requires: xdg-utils
|
||||||
|
|
||||||
|
@ -25,19 +25,19 @@ BuildRequires: git
|
|||||||
%package -n python3-%{pypi_name}
|
%package -n python3-%{pypi_name}
|
||||||
Summary: %{summary}
|
Summary: %{summary}
|
||||||
%{?python_provide:%python_provide python3-%{pypi_name}}
|
%{?python_provide:%python_provide python3-%{pypi_name}}
|
||||||
Requires: python3dist(babel)
|
Requires: python3-babel
|
||||||
Requires: python3dist(evdev)
|
Requires: python3-evdev
|
||||||
Requires: python3dist(icoextract)
|
Requires: python3-icoextract
|
||||||
Requires: python3dist(numpy)
|
Requires: python3-numpy
|
||||||
Requires: python3dist(orjson)
|
Requires: python3-orjson
|
||||||
Requires: python3dist(psutil)
|
Requires: python3-psutil
|
||||||
Requires: python3dist(pyside6)
|
Requires: python3-pyside6
|
||||||
Requires: python3dist(pyudev)
|
Requires: python3-pyudev
|
||||||
Requires: python3dist(requests)
|
Requires: python3-requests
|
||||||
Requires: python3dist(tqdm)
|
Requires: python3-tqdm
|
||||||
Requires: python3dist(vdf)
|
Requires: python3-vdf
|
||||||
Requires: python3dist(pefile)
|
Requires: python3-pefile
|
||||||
Requires: python3dist(pillow)
|
Requires: python3-pillow
|
||||||
Requires: perl-Image-ExifTool
|
Requires: perl-Image-ExifTool
|
||||||
Requires: xdg-utils
|
Requires: xdg-utils
|
||||||
|
|
||||||
|
8
config.js
Normal file
8
config.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
"endpoint": "https://git.linux-gaming.ru/api/v1",
|
||||||
|
"gitAuthor": "Renovate Bot <noreply@linux-gaming.ru>",
|
||||||
|
"platform": "gitea",
|
||||||
|
"onboardingConfigFileName": "renovate.json",
|
||||||
|
"autodiscover": true,
|
||||||
|
"optimizeForDisabled": true,
|
||||||
|
};
|
@ -14,3 +14,5 @@ MIME-Version:
|
|||||||
Content-Type:
|
Content-Type:
|
||||||
Content-Transfer-Encoding:
|
Content-Transfer-Encoding:
|
||||||
Generated-By:
|
Generated-By:
|
||||||
|
start.sh
|
||||||
|
EGS
|
||||||
|
@ -20,9 +20,9 @@ Current translation status:
|
|||||||
|
|
||||||
| Locale | Progress | Translated |
|
| Locale | Progress | Translated |
|
||||||
| :----- | -------: | ---------: |
|
| :----- | -------: | ---------: |
|
||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 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 161 |
|
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 194 |
|
||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 161 of 161 |
|
| [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 |
|
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 194 |
|
||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 161 |
|
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 194 |
|
||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 161 из 161 |
|
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 194 из 194 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
from PySide6.QtCore import QLocale, QTranslator, QLibraryInfo
|
from PySide6.QtCore import QLocale, QTranslator, QLibraryInfo
|
||||||
from PySide6.QtWidgets import QApplication
|
from PySide6.QtWidgets import QApplication
|
||||||
from PySide6.QtGui import QIcon
|
from PySide6.QtGui import QIcon
|
||||||
@ -33,6 +35,13 @@ def main():
|
|||||||
|
|
||||||
window = MainWindow()
|
window = MainWindow()
|
||||||
|
|
||||||
|
if args.session:
|
||||||
|
gamescope_cmd = os.getenv("GAMESCOPE_CMD", "gamescope -f --xwayland-count 2")
|
||||||
|
cmd = f"{gamescope_cmd} -- portprotonqt"
|
||||||
|
logger.info(f"Executing: {cmd}")
|
||||||
|
subprocess.Popen(cmd, shell=True)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if args.fullscreen:
|
if args.fullscreen:
|
||||||
logger.info("Launching in fullscreen mode due to --fullscreen flag")
|
logger.info("Launching in fullscreen mode due to --fullscreen flag")
|
||||||
save_fullscreen_config(True)
|
save_fullscreen_config(True)
|
||||||
|
@ -13,4 +13,9 @@ def parse_args():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Запустить приложение в полноэкранном режиме и сохранить эту настройку"
|
help="Запустить приложение в полноэкранном режиме и сохранить эту настройку"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--session",
|
||||||
|
action="store_true",
|
||||||
|
help="Запустить приложение с использованием gamescope"
|
||||||
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -95,6 +95,7 @@ class AddGameDialog(QDialog):
|
|||||||
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
|
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
|
||||||
|
|
||||||
layout = QFormLayout(self)
|
layout = QFormLayout(self)
|
||||||
|
layout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||||
|
|
||||||
# Game name
|
# Game name
|
||||||
self.nameEdit = QLineEdit(self)
|
self.nameEdit = QLineEdit(self)
|
||||||
|
@ -12,9 +12,42 @@ from collections.abc import Callable
|
|||||||
from portprotonqt.localization import get_egs_language, _
|
from portprotonqt.localization import get_egs_language, _
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
from portprotonqt.image_utils import load_pixmap_async
|
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
|
from PySide6.QtGui import QPixmap
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
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:
|
def get_cache_dir() -> Path:
|
||||||
"""Returns the path to the cache directory, creating it if necessary."""
|
"""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)
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||||
return cache_dir
|
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(
|
def get_egs_game_description_async(
|
||||||
app_name: str,
|
app_name: str,
|
||||||
callback: Callable[[str], None],
|
callback: Callable[[str], None],
|
||||||
@ -281,6 +545,7 @@ def get_egs_game_description_async(
|
|||||||
|
|
||||||
thread = threading.Thread(target=fetch_description, daemon=True)
|
thread = threading.Thread(target=fetch_description, daemon=True)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def run_legendary_list_async(legendary_path: str, callback: Callable[[list | None], None]):
|
def run_legendary_list_async(legendary_path: str, callback: Callable[[list | None], None]):
|
||||||
"""
|
"""
|
||||||
Асинхронно выполняет команду 'legendary list --json' и возвращает результат через callback.
|
Асинхронно выполняет команду '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]):
|
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.
|
Асинхронно загружает Epic Games Store игры с использованием legendary CLI.
|
||||||
|
Читает статистику времени игры и последнего запуска из файла statistics.
|
||||||
|
Проверяет наличие игры в Steam для получения ProtonDB статуса.
|
||||||
"""
|
"""
|
||||||
logger.debug("Starting to load Epic Games Store games")
|
logger.debug("Starting to load Epic Games Store games")
|
||||||
games: list[tuple] = []
|
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_file = cache_dir / "legendary_games.json"
|
||||||
cache_ttl = 3600 # Cache TTL in seconds (1 hour)
|
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):
|
if not os.path.exists(legendary_path):
|
||||||
logger.info("Legendary binary not found, downloading...")
|
logger.info("Legendary binary not found, downloading...")
|
||||||
def on_legendary_downloaded(result):
|
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}")
|
logger.error(f"Failed to make legendary binary executable: {e}")
|
||||||
callback(games) # Return empty games list on failure
|
callback(games) # Return empty games list on failure
|
||||||
return
|
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:
|
else:
|
||||||
logger.error("Failed to download legendary binary")
|
logger.error("Failed to download legendary binary")
|
||||||
callback(games) # Return empty games list on failure
|
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)
|
callback(games)
|
||||||
return
|
return
|
||||||
else:
|
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.
|
Продолжает процесс загрузки EGS игр, либо из кэша, либо через legendary CLI.
|
||||||
"""
|
"""
|
||||||
@ -410,6 +685,33 @@ def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tu
|
|||||||
callback(final_games)
|
callback(final_games)
|
||||||
return
|
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"
|
metadata_file = metadata_dir / f"{app_name}.json"
|
||||||
cover_url = ""
|
cover_url = ""
|
||||||
try:
|
try:
|
||||||
@ -426,11 +728,16 @@ 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")
|
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 ""
|
local_path = os.path.join(image_folder, f"{app_name}.jpg") if cover_url else ""
|
||||||
|
|
||||||
|
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_protondb_tier(protondb_tier: str):
|
||||||
def on_description_fetched(api_description: str):
|
def on_description_fetched(api_description: str):
|
||||||
final_description = api_description or _("No description available")
|
final_description = api_description or _("No description available")
|
||||||
|
|
||||||
def on_cover_loaded(pixmap: QPixmap):
|
def on_cover_loaded(pixmap: QPixmap):
|
||||||
from portprotonqt.steam_api import get_weanticheatyet_status_async
|
|
||||||
def on_anticheat_status(status: str):
|
def on_anticheat_status(status: str):
|
||||||
nonlocal pending_images
|
nonlocal pending_images
|
||||||
with results_lock:
|
with results_lock:
|
||||||
@ -441,12 +748,12 @@ def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tu
|
|||||||
app_name,
|
app_name,
|
||||||
f"legendary:launch:{app_name}",
|
f"legendary:launch:{app_name}",
|
||||||
"",
|
"",
|
||||||
_("Never"),
|
last_launch, # Время последнего запуска
|
||||||
"",
|
formatted_playtime, # Форматированное время игры
|
||||||
"",
|
protondb_tier, # ProtonDB tier
|
||||||
status or "",
|
status or "",
|
||||||
0,
|
last_launch_timestamp, # Временная метка последнего запуска
|
||||||
0,
|
playtime_seconds, # Время игры в секундах
|
||||||
"epic"
|
"epic"
|
||||||
)
|
)
|
||||||
pending_images -= 1
|
pending_images -= 1
|
||||||
@ -461,6 +768,15 @@ def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tu
|
|||||||
|
|
||||||
get_egs_game_description_async(title, on_description_fetched)
|
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))
|
max_workers = min(4, len(valid_games))
|
||||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||||
for i, game in enumerate(valid_games):
|
for i, game in enumerate(valid_games):
|
||||||
|
@ -25,6 +25,8 @@ class GameCard(QFrame):
|
|||||||
addToSteamRequested = Signal(str, str, str) # name, exec_line, cover_path
|
addToSteamRequested = Signal(str, str, str) # name, exec_line, cover_path
|
||||||
removeFromSteamRequested = Signal(str, str) # name, exec_line
|
removeFromSteamRequested = Signal(str, str) # name, exec_line
|
||||||
openGameFolderRequested = Signal(str, str) # name, exec_line
|
openGameFolderRequested = Signal(str, str) # name, exec_line
|
||||||
|
hoverChanged = Signal(str, bool)
|
||||||
|
focusChanged = Signal(str, bool)
|
||||||
|
|
||||||
def __init__(self, name, description, cover_path, appid, controller_support, exec_line,
|
def __init__(self, name, description, cover_path, appid, controller_support, exec_line,
|
||||||
last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, game_source,
|
last_launch, formatted_playtime, protondb_tier, anticheat_status, last_launch_ts, playtime_seconds, game_source,
|
||||||
@ -66,14 +68,14 @@ class GameCard(QFrame):
|
|||||||
self.setStyleSheet(self.theme.GAME_CARD_WINDOW_STYLE)
|
self.setStyleSheet(self.theme.GAME_CARD_WINDOW_STYLE)
|
||||||
|
|
||||||
# Параметры анимации обводки
|
# Параметры анимации обводки
|
||||||
self._borderWidth = 2
|
self._borderWidth = self.theme.GAME_CARD_ANIMATION["default_border_width"]
|
||||||
self._gradientAngle = 0.0
|
self._gradientAngle = self.theme.GAME_CARD_ANIMATION["gradient_start_angle"]
|
||||||
self._hovered = False
|
self._hovered = False
|
||||||
self._focused = False
|
self._focused = False
|
||||||
|
|
||||||
# Анимации
|
# Анимации
|
||||||
self.thickness_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
|
self.thickness_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
|
||||||
self.thickness_anim.setDuration(300)
|
self.thickness_anim.setDuration(self.theme.GAME_CARD_ANIMATION["thickness_anim_duration"])
|
||||||
self.gradient_anim = None
|
self.gradient_anim = None
|
||||||
self.pulse_anim = None
|
self.pulse_anim = None
|
||||||
|
|
||||||
@ -169,7 +171,7 @@ class GameCard(QFrame):
|
|||||||
self.steamLabel.setVisible(self.steam_visible)
|
self.steamLabel.setVisible(self.steam_visible)
|
||||||
|
|
||||||
# Epic Games Store бейдж
|
# Epic Games Store бейдж
|
||||||
egs_icon = self.theme_manager.get_icon("steam")
|
egs_icon = self.theme_manager.get_icon("epic_games")
|
||||||
self.egsLabel = ClickableLabel(
|
self.egsLabel = ClickableLabel(
|
||||||
"Epic Games",
|
"Epic Games",
|
||||||
icon=egs_icon,
|
icon=egs_icon,
|
||||||
@ -447,10 +449,8 @@ class GameCard(QFrame):
|
|||||||
if self._hovered or self._focused:
|
if self._hovered or self._focused:
|
||||||
center = self.rect().center()
|
center = self.rect().center()
|
||||||
gradient = QConicalGradient(center, self._gradientAngle)
|
gradient = QConicalGradient(center, self._gradientAngle)
|
||||||
gradient.setColorAt(0, QColor("#00fff5"))
|
for stop in self.theme.GAME_CARD_ANIMATION["gradient_colors"]:
|
||||||
gradient.setColorAt(0.33, QColor("#FF5733"))
|
gradient.setColorAt(stop["position"], QColor(stop["color"]))
|
||||||
gradient.setColorAt(0.66, QColor("#9B59B6"))
|
|
||||||
gradient.setColorAt(1, QColor("#00fff5"))
|
|
||||||
pen.setBrush(QBrush(gradient))
|
pen.setBrush(QBrush(gradient))
|
||||||
else:
|
else:
|
||||||
pen.setColor(QColor(0, 0, 0, 0))
|
pen.setColor(QColor(0, 0, 0, 0))
|
||||||
@ -467,22 +467,25 @@ class GameCard(QFrame):
|
|||||||
if self.pulse_anim:
|
if self.pulse_anim:
|
||||||
self.pulse_anim.stop()
|
self.pulse_anim.stop()
|
||||||
self.pulse_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
|
self.pulse_anim = QPropertyAnimation(self, QByteArray(b"borderWidth"))
|
||||||
self.pulse_anim.setDuration(800)
|
self.pulse_anim.setDuration(self.theme.GAME_CARD_ANIMATION["pulse_anim_duration"])
|
||||||
self.pulse_anim.setLoopCount(0)
|
self.pulse_anim.setLoopCount(0)
|
||||||
self.pulse_anim.setKeyValueAt(0, 8)
|
self.pulse_anim.setKeyValueAt(0, self.theme.GAME_CARD_ANIMATION["pulse_min_border_width"])
|
||||||
self.pulse_anim.setKeyValueAt(0.5, 10)
|
self.pulse_anim.setKeyValueAt(0.5, self.theme.GAME_CARD_ANIMATION["pulse_max_border_width"])
|
||||||
self.pulse_anim.setKeyValueAt(1, 8)
|
self.pulse_anim.setKeyValueAt(1, self.theme.GAME_CARD_ANIMATION["pulse_min_border_width"])
|
||||||
self.pulse_anim.start()
|
self.pulse_anim.start()
|
||||||
|
|
||||||
def enterEvent(self, event):
|
def enterEvent(self, event):
|
||||||
self._hovered = True
|
self._hovered = True
|
||||||
|
self.hoverChanged.emit(self.name, True)
|
||||||
|
self.setFocus(Qt.FocusReason.MouseFocusReason)
|
||||||
|
|
||||||
self.thickness_anim.stop()
|
self.thickness_anim.stop()
|
||||||
if self._isPulseAnimationConnected:
|
if self._isPulseAnimationConnected:
|
||||||
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
||||||
self._isPulseAnimationConnected = False
|
self._isPulseAnimationConnected = False
|
||||||
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.OutBack))
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve"]]))
|
||||||
self.thickness_anim.setStartValue(self._borderWidth)
|
self.thickness_anim.setStartValue(self._borderWidth)
|
||||||
self.thickness_anim.setEndValue(8)
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["hover_border_width"])
|
||||||
self.thickness_anim.finished.connect(self.startPulseAnimation)
|
self.thickness_anim.finished.connect(self.startPulseAnimation)
|
||||||
self._isPulseAnimationConnected = True
|
self._isPulseAnimationConnected = True
|
||||||
self.thickness_anim.start()
|
self.thickness_anim.start()
|
||||||
@ -490,9 +493,9 @@ class GameCard(QFrame):
|
|||||||
if self.gradient_anim:
|
if self.gradient_anim:
|
||||||
self.gradient_anim.stop()
|
self.gradient_anim.stop()
|
||||||
self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
|
self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
|
||||||
self.gradient_anim.setDuration(3000)
|
self.gradient_anim.setDuration(self.theme.GAME_CARD_ANIMATION["gradient_anim_duration"])
|
||||||
self.gradient_anim.setStartValue(360)
|
self.gradient_anim.setStartValue(self.theme.GAME_CARD_ANIMATION["gradient_start_angle"])
|
||||||
self.gradient_anim.setEndValue(0)
|
self.gradient_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["gradient_end_angle"])
|
||||||
self.gradient_anim.setLoopCount(-1)
|
self.gradient_anim.setLoopCount(-1)
|
||||||
self.gradient_anim.start()
|
self.gradient_anim.start()
|
||||||
|
|
||||||
@ -500,33 +503,37 @@ class GameCard(QFrame):
|
|||||||
|
|
||||||
def leaveEvent(self, event):
|
def leaveEvent(self, event):
|
||||||
self._hovered = False
|
self._hovered = False
|
||||||
if not self._focused: # Сохраняем анимацию, если есть фокус
|
self.hoverChanged.emit(self.name, False)
|
||||||
|
if not self._focused:
|
||||||
if self.gradient_anim:
|
if self.gradient_anim:
|
||||||
self.gradient_anim.stop()
|
self.gradient_anim.stop()
|
||||||
self.gradient_anim = None
|
self.gradient_anim = None
|
||||||
self.thickness_anim.stop()
|
|
||||||
if self._isPulseAnimationConnected:
|
|
||||||
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
|
||||||
self._isPulseAnimationConnected = False
|
|
||||||
if self.pulse_anim:
|
if self.pulse_anim:
|
||||||
self.pulse_anim.stop()
|
self.pulse_anim.stop()
|
||||||
self.pulse_anim = None
|
self.pulse_anim = None
|
||||||
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
|
if self.thickness_anim:
|
||||||
self.thickness_anim.setStartValue(self._borderWidth)
|
|
||||||
self.thickness_anim.setEndValue(2)
|
|
||||||
self.thickness_anim.start()
|
|
||||||
|
|
||||||
super().leaveEvent(event)
|
|
||||||
|
|
||||||
def focusInEvent(self, event):
|
|
||||||
self._focused = True
|
|
||||||
self.thickness_anim.stop()
|
self.thickness_anim.stop()
|
||||||
if self._isPulseAnimationConnected:
|
if self._isPulseAnimationConnected:
|
||||||
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
||||||
self._isPulseAnimationConnected = False
|
self._isPulseAnimationConnected = False
|
||||||
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.OutBack))
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
|
||||||
self.thickness_anim.setStartValue(self._borderWidth)
|
self.thickness_anim.setStartValue(self._borderWidth)
|
||||||
self.thickness_anim.setEndValue(12)
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["default_border_width"])
|
||||||
|
self.thickness_anim.start()
|
||||||
|
super().leaveEvent(event)
|
||||||
|
|
||||||
|
def focusInEvent(self, event):
|
||||||
|
if not self._hovered:
|
||||||
|
self._focused = True
|
||||||
|
self.focusChanged.emit(self.name, True)
|
||||||
|
|
||||||
|
self.thickness_anim.stop()
|
||||||
|
if self._isPulseAnimationConnected:
|
||||||
|
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
||||||
|
self._isPulseAnimationConnected = False
|
||||||
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve"]]))
|
||||||
|
self.thickness_anim.setStartValue(self._borderWidth)
|
||||||
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["focus_border_width"])
|
||||||
self.thickness_anim.finished.connect(self.startPulseAnimation)
|
self.thickness_anim.finished.connect(self.startPulseAnimation)
|
||||||
self._isPulseAnimationConnected = True
|
self._isPulseAnimationConnected = True
|
||||||
self.thickness_anim.start()
|
self.thickness_anim.start()
|
||||||
@ -534,9 +541,9 @@ class GameCard(QFrame):
|
|||||||
if self.gradient_anim:
|
if self.gradient_anim:
|
||||||
self.gradient_anim.stop()
|
self.gradient_anim.stop()
|
||||||
self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
|
self.gradient_anim = QPropertyAnimation(self, QByteArray(b"gradientAngle"))
|
||||||
self.gradient_anim.setDuration(3000)
|
self.gradient_anim.setDuration(self.theme.GAME_CARD_ANIMATION["gradient_anim_duration"])
|
||||||
self.gradient_anim.setStartValue(360)
|
self.gradient_anim.setStartValue(self.theme.GAME_CARD_ANIMATION["gradient_start_angle"])
|
||||||
self.gradient_anim.setEndValue(0)
|
self.gradient_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["gradient_end_angle"])
|
||||||
self.gradient_anim.setLoopCount(-1)
|
self.gradient_anim.setLoopCount(-1)
|
||||||
self.gradient_anim.start()
|
self.gradient_anim.start()
|
||||||
|
|
||||||
@ -544,22 +551,23 @@ class GameCard(QFrame):
|
|||||||
|
|
||||||
def focusOutEvent(self, event):
|
def focusOutEvent(self, event):
|
||||||
self._focused = False
|
self._focused = False
|
||||||
if not self._hovered: # Сохраняем анимацию, если есть наведение
|
self.focusChanged.emit(self.name, False)
|
||||||
|
if not self._hovered:
|
||||||
if self.gradient_anim:
|
if self.gradient_anim:
|
||||||
self.gradient_anim.stop()
|
self.gradient_anim.stop()
|
||||||
self.gradient_anim = None
|
self.gradient_anim = None
|
||||||
|
if self.pulse_anim:
|
||||||
|
self.pulse_anim.stop()
|
||||||
|
self.pulse_anim = None
|
||||||
|
if self.thickness_anim:
|
||||||
self.thickness_anim.stop()
|
self.thickness_anim.stop()
|
||||||
if self._isPulseAnimationConnected:
|
if self._isPulseAnimationConnected:
|
||||||
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
self.thickness_anim.finished.disconnect(self.startPulseAnimation)
|
||||||
self._isPulseAnimationConnected = False
|
self._isPulseAnimationConnected = False
|
||||||
if self.pulse_anim:
|
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type[self.theme.GAME_CARD_ANIMATION["thickness_easing_curve_out"]]))
|
||||||
self.pulse_anim.stop()
|
|
||||||
self.pulse_anim = None
|
|
||||||
self.thickness_anim.setEasingCurve(QEasingCurve(QEasingCurve.Type.InBack))
|
|
||||||
self.thickness_anim.setStartValue(self._borderWidth)
|
self.thickness_anim.setStartValue(self._borderWidth)
|
||||||
self.thickness_anim.setEndValue(2)
|
self.thickness_anim.setEndValue(self.theme.GAME_CARD_ANIMATION["default_border_width"])
|
||||||
self.thickness_anim.start()
|
self.thickness_anim.start()
|
||||||
|
|
||||||
super().focusOutEvent(event)
|
super().focusOutEvent(event)
|
||||||
|
|
||||||
def mousePressEvent(self, event):
|
def mousePressEvent(self, event):
|
||||||
|
Binary file not shown.
@ -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-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"
|
"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,31 +26,70 @@ msgstr ""
|
|||||||
msgid "Add to Favorites"
|
msgid "Add to Favorites"
|
||||||
msgstr ""
|
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"
|
msgid "Remove from Desktop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add to Desktop"
|
msgid "Add to Desktop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Shortcut"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Delete from PortProton"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Open Game Folder"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Remove from Menu"
|
msgid "Remove from Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add to Menu"
|
msgid "Add to Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Remove from Steam"
|
msgid "Edit Shortcut"
|
||||||
msgstr ""
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -61,14 +100,51 @@ msgstr ""
|
|||||||
msgid "Removed '{0}' from favorites"
|
msgid "Removed '{0}' from favorites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "PortProton is not found."
|
msgid "PortProton is not found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -76,7 +152,7 @@ msgid "Failed to parse .desktop file for game: {0}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Error reading .desktop file: {0}"
|
msgid "Failed to read .desktop file: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -105,7 +181,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Could not locate .desktop file for '{0}'"
|
msgid "Could not locate .desktop file for game: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -121,35 +197,7 @@ msgid "Failed to delete custom data: {0}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Game '{0}' added to menu"
|
msgid "Failed to add game '{0}' to desktop: {1}"
|
||||||
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"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Game name and executable path are required."
|
msgid "Game name and executable path are required."
|
||||||
@ -174,25 +222,54 @@ msgstr ""
|
|||||||
msgid "Failed to copy cover image: {0}"
|
msgid "Failed to copy cover image: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Restart Steam"
|
#, python-brace-format
|
||||||
msgstr ""
|
msgid "Failed to add game '{0}' to Steam: {1}"
|
||||||
|
|
||||||
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."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
@ -235,10 +312,10 @@ msgstr ""
|
|||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No description available"
|
msgid "Never"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Never"
|
msgid "No description available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Supported"
|
msgid "Supported"
|
||||||
@ -382,6 +459,21 @@ msgstr ""
|
|||||||
msgid "Gamepad haptic feedback:"
|
msgid "Gamepad haptic feedback:"
|
||||||
msgstr ""
|
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"
|
msgid "Save Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -397,6 +489,22 @@ msgstr ""
|
|||||||
msgid "Failed to open Legendary login page"
|
msgid "Failed to open Legendary login page"
|
||||||
msgstr ""
|
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"
|
msgid "Confirm Reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -478,6 +586,20 @@ msgstr ""
|
|||||||
msgid "Play"
|
msgid "Play"
|
||||||
msgstr ""
|
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)"
|
msgid "Invalid command format (native)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -488,12 +610,6 @@ msgstr ""
|
|||||||
msgid "File not found: {0}"
|
msgid "File not found: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cannot launch game while another game is running"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Launching"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Reboot"
|
msgid "Reboot"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
Binary file not shown.
@ -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-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"
|
"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,31 +26,70 @@ msgstr ""
|
|||||||
msgid "Add to Favorites"
|
msgid "Add to Favorites"
|
||||||
msgstr ""
|
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"
|
msgid "Remove from Desktop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add to Desktop"
|
msgid "Add to Desktop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Shortcut"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Delete from PortProton"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Open Game Folder"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Remove from Menu"
|
msgid "Remove from Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add to Menu"
|
msgid "Add to Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Remove from Steam"
|
msgid "Edit Shortcut"
|
||||||
msgstr ""
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -61,14 +100,51 @@ msgstr ""
|
|||||||
msgid "Removed '{0}' from favorites"
|
msgid "Removed '{0}' from favorites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "PortProton is not found."
|
msgid "PortProton is not found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -76,7 +152,7 @@ msgid "Failed to parse .desktop file for game: {0}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Error reading .desktop file: {0}"
|
msgid "Failed to read .desktop file: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -105,7 +181,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Could not locate .desktop file for '{0}'"
|
msgid "Could not locate .desktop file for game: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -121,35 +197,7 @@ msgid "Failed to delete custom data: {0}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Game '{0}' added to menu"
|
msgid "Failed to add game '{0}' to desktop: {1}"
|
||||||
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"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Game name and executable path are required."
|
msgid "Game name and executable path are required."
|
||||||
@ -174,25 +222,54 @@ msgstr ""
|
|||||||
msgid "Failed to copy cover image: {0}"
|
msgid "Failed to copy cover image: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Restart Steam"
|
#, python-brace-format
|
||||||
msgstr ""
|
msgid "Failed to add game '{0}' to Steam: {1}"
|
||||||
|
|
||||||
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."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
@ -235,10 +312,10 @@ msgstr ""
|
|||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No description available"
|
msgid "Never"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Never"
|
msgid "No description available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Supported"
|
msgid "Supported"
|
||||||
@ -382,6 +459,21 @@ msgstr ""
|
|||||||
msgid "Gamepad haptic feedback:"
|
msgid "Gamepad haptic feedback:"
|
||||||
msgstr ""
|
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"
|
msgid "Save Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -397,6 +489,22 @@ msgstr ""
|
|||||||
msgid "Failed to open Legendary login page"
|
msgid "Failed to open Legendary login page"
|
||||||
msgstr ""
|
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"
|
msgid "Confirm Reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -478,6 +586,20 @@ msgstr ""
|
|||||||
msgid "Play"
|
msgid "Play"
|
||||||
msgstr ""
|
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)"
|
msgid "Invalid command format (native)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -488,12 +610,6 @@ msgstr ""
|
|||||||
msgid "File not found: {0}"
|
msgid "File not found: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cannot launch game while another game is running"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Launching"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Reboot"
|
msgid "Reboot"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -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-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"
|
"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,31 +24,70 @@ msgstr ""
|
|||||||
msgid "Add to Favorites"
|
msgid "Add to Favorites"
|
||||||
msgstr ""
|
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"
|
msgid "Remove from Desktop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add to Desktop"
|
msgid "Add to Desktop"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Shortcut"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Delete from PortProton"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Open Game Folder"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Remove from Menu"
|
msgid "Remove from Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Add to Menu"
|
msgid "Add to Menu"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Remove from Steam"
|
msgid "Edit Shortcut"
|
||||||
msgstr ""
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -59,14 +98,51 @@ msgstr ""
|
|||||||
msgid "Removed '{0}' from favorites"
|
msgid "Removed '{0}' from favorites"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "PortProton is not found."
|
msgid "PortProton is not found."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -74,7 +150,7 @@ msgid "Failed to parse .desktop file for game: {0}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Error reading .desktop file: {0}"
|
msgid "Failed to read .desktop file: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -103,7 +179,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Could not locate .desktop file for '{0}'"
|
msgid "Could not locate .desktop file for game: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@ -119,35 +195,7 @@ msgid "Failed to delete custom data: {0}"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Game '{0}' added to menu"
|
msgid "Failed to add game '{0}' to desktop: {1}"
|
||||||
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"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Game name and executable path are required."
|
msgid "Game name and executable path are required."
|
||||||
@ -172,25 +220,54 @@ msgstr ""
|
|||||||
msgid "Failed to copy cover image: {0}"
|
msgid "Failed to copy cover image: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Restart Steam"
|
#, python-brace-format
|
||||||
msgstr ""
|
msgid "Failed to add game '{0}' to Steam: {1}"
|
||||||
|
|
||||||
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."
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, 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 ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Edit Game"
|
msgid "Edit Game"
|
||||||
@ -233,10 +310,10 @@ msgstr ""
|
|||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "No description available"
|
msgid "Never"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Never"
|
msgid "No description available"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Supported"
|
msgid "Supported"
|
||||||
@ -380,6 +457,21 @@ msgstr ""
|
|||||||
msgid "Gamepad haptic feedback:"
|
msgid "Gamepad haptic feedback:"
|
||||||
msgstr ""
|
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"
|
msgid "Save Settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -395,6 +487,22 @@ msgstr ""
|
|||||||
msgid "Failed to open Legendary login page"
|
msgid "Failed to open Legendary login page"
|
||||||
msgstr ""
|
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"
|
msgid "Confirm Reset"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -476,6 +584,20 @@ msgstr ""
|
|||||||
msgid "Play"
|
msgid "Play"
|
||||||
msgstr ""
|
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)"
|
msgid "Invalid command format (native)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -486,12 +608,6 @@ msgstr ""
|
|||||||
msgid "File not found: {0}"
|
msgid "File not found: {0}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cannot launch game while another game is running"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Launching"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Reboot"
|
msgid "Reboot"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
Binary file not shown.
@ -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-06-14 10:37+0500\n"
|
"POT-Creation-Date: 2025-06-22 18:23+0500\n"
|
||||||
"PO-Revision-Date: 2025-06-14 10:37+0500\n"
|
"PO-Revision-Date: 2025-06-22 18:22+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,32 +27,73 @@ msgstr "Удалить из Избранного"
|
|||||||
msgid "Add to Favorites"
|
msgid "Add to Favorites"
|
||||||
msgstr "Добавить в Избранное"
|
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"
|
msgid "Remove from Desktop"
|
||||||
msgstr "Удалить с рабочего стола"
|
msgstr "Удалить с рабочего стола"
|
||||||
|
|
||||||
msgid "Add to Desktop"
|
msgid "Add to Desktop"
|
||||||
msgstr "Добавить на рабочий стол"
|
msgstr "Добавить на рабочий стол"
|
||||||
|
|
||||||
msgid "Edit Shortcut"
|
|
||||||
msgstr "Редактировать"
|
|
||||||
|
|
||||||
msgid "Delete from PortProton"
|
|
||||||
msgstr "Удалить из PortProton"
|
|
||||||
|
|
||||||
msgid "Open Game Folder"
|
|
||||||
msgstr "Открыть папку с игрой"
|
|
||||||
|
|
||||||
msgid "Remove from Menu"
|
msgid "Remove from Menu"
|
||||||
msgstr "Удалить из меню"
|
msgstr "Удалить из меню"
|
||||||
|
|
||||||
msgid "Add to Menu"
|
msgid "Add to Menu"
|
||||||
msgstr "Добавить в меню"
|
msgstr "Добавить в меню"
|
||||||
|
|
||||||
msgid "Remove from Steam"
|
msgid "Edit Shortcut"
|
||||||
msgstr "Удалить из Steam"
|
msgstr "Редактировать"
|
||||||
|
|
||||||
msgid "Add to Steam"
|
msgid "Delete from PortProton"
|
||||||
msgstr "Добавить в Steam"
|
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
|
#, python-brace-format
|
||||||
msgid "Added '{0}' to favorites"
|
msgid "Added '{0}' to favorites"
|
||||||
@ -62,23 +103,60 @@ msgstr "Добавление '{0}' в избранное"
|
|||||||
msgid "Removed '{0}' from favorites"
|
msgid "Removed '{0}' from favorites"
|
||||||
msgstr "Удаление '{0}' из избранного"
|
msgstr "Удаление '{0}' из избранного"
|
||||||
|
|
||||||
msgid "Error"
|
|
||||||
msgstr "Ошибка"
|
|
||||||
|
|
||||||
msgid "PortProton is not found."
|
msgid "PortProton is not found."
|
||||||
msgstr "PortProton не найден."
|
msgstr "PortProton не найден."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "No executable command found in .desktop for game: {0}"
|
msgid "start.sh not found at {0}"
|
||||||
msgstr "Не найдено ни одной исполняемой команды для игры: {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
|
#, python-brace-format
|
||||||
msgid "Failed to parse .desktop file for game: {0}"
|
msgid "Failed to parse .desktop file for game: {0}"
|
||||||
msgstr "Не удалось удалить файл .desktop: {0}"
|
msgstr "Не удалось удалить файл .desktop: {0}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Error reading .desktop file: {0}"
|
msgid "Failed to read .desktop file: {0}"
|
||||||
msgstr "Не удалось удалить файл .desktop: {0}"
|
msgstr "Не удалось прочитать файл .desktop: {0}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid ".desktop file not found for game: {0}"
|
msgid ".desktop file not found for game: {0}"
|
||||||
@ -108,8 +186,8 @@ msgstr ""
|
|||||||
".desktop и настраиваемых данных."
|
".desktop и настраиваемых данных."
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Could not locate .desktop file for '{0}'"
|
msgid "Could not locate .desktop file for game: {0}"
|
||||||
msgstr "Не удалось найти файл .desktop для '{0}'"
|
msgstr "Не удалось найти файл .desktop для игры: {0}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to delete .desktop file: {0}"
|
msgid "Failed to delete .desktop file: {0}"
|
||||||
@ -124,36 +202,8 @@ msgid "Failed to delete custom data: {0}"
|
|||||||
msgstr "Не удалось удалить настраиваемые данные: {0}"
|
msgstr "Не удалось удалить настраиваемые данные: {0}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Game '{0}' added to menu"
|
msgid "Failed to add game '{0}' to desktop: {1}"
|
||||||
msgstr "Игра '{0}' добавлена в меню"
|
msgstr "Не удалось добавить игру '{0}' на рабочий стол: {1}"
|
||||||
|
|
||||||
#, 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 "Game name and executable path are required."
|
msgid "Game name and executable path are required."
|
||||||
msgstr "Необходимо указать название игры и путь к исполняемому файлу."
|
msgstr "Необходимо указать название игры и путь к исполняемому файлу."
|
||||||
@ -177,30 +227,57 @@ msgstr "Не удалось удалить файл .desktop: {0}"
|
|||||||
msgid "Failed to copy cover image: {0}"
|
msgid "Failed to copy cover image: {0}"
|
||||||
msgstr "Не удалось удалить игру из меню: {0}"
|
msgstr "Не удалось удалить игру из меню: {0}"
|
||||||
|
|
||||||
msgid "Restart Steam"
|
#, python-brace-format
|
||||||
msgstr "Перезапустите Steam"
|
msgid "Failed to add game '{0}' to Steam: {1}"
|
||||||
|
msgstr "Не удалось добавить игру '{0}' в Steam: {1}"
|
||||||
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
|
#, python-brace-format
|
||||||
msgid "Opened folder for '{0}'"
|
msgid ""
|
||||||
msgstr "Открытие папки для '{0}'"
|
"'{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
|
#, python-brace-format
|
||||||
msgid "Failed to open game folder: {0}"
|
msgid "Failed to create backup of shortcuts.vdf: {0}"
|
||||||
msgstr "Не удалось открыть папку для игры: {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"
|
msgid "Edit Game"
|
||||||
msgstr "Редактировать игру"
|
msgstr "Редактировать игру"
|
||||||
@ -242,12 +319,12 @@ msgstr "Запустить игру \"{name}\" с помощью PortProton"
|
|||||||
msgid "Loading Epic Games Store games..."
|
msgid "Loading Epic Games Store games..."
|
||||||
msgstr "Загрузка игр из Epic Games Store..."
|
msgstr "Загрузка игр из Epic Games Store..."
|
||||||
|
|
||||||
msgid "No description available"
|
|
||||||
msgstr "Описание не найдено"
|
|
||||||
|
|
||||||
msgid "Never"
|
msgid "Never"
|
||||||
msgstr "Никогда"
|
msgstr "Никогда"
|
||||||
|
|
||||||
|
msgid "No description available"
|
||||||
|
msgstr "Описание не найдено"
|
||||||
|
|
||||||
msgid "Supported"
|
msgid "Supported"
|
||||||
msgstr "Поддерживается"
|
msgstr "Поддерживается"
|
||||||
|
|
||||||
@ -389,6 +466,21 @@ msgstr "Тактильная отдача на геймпаде"
|
|||||||
msgid "Gamepad haptic feedback:"
|
msgid "Gamepad haptic feedback:"
|
||||||
msgstr "Тактильная отдача на геймпаде:"
|
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"
|
msgid "Save Settings"
|
||||||
msgstr "Сохранить настройки"
|
msgstr "Сохранить настройки"
|
||||||
|
|
||||||
@ -404,6 +496,22 @@ msgstr "Открытие страницы входа в Legendary в брауз
|
|||||||
msgid "Failed to open Legendary login page"
|
msgid "Failed to open Legendary login page"
|
||||||
msgstr "Не удалось открыть страницу входа в Legendary"
|
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"
|
msgid "Confirm Reset"
|
||||||
msgstr "Подтвердите удаление"
|
msgstr "Подтвердите удаление"
|
||||||
|
|
||||||
@ -487,6 +595,20 @@ msgstr "Остановить"
|
|||||||
msgid "Play"
|
msgid "Play"
|
||||||
msgstr "Играть"
|
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)"
|
msgid "Invalid command format (native)"
|
||||||
msgstr "Неправильный формат команды (нативная версия)"
|
msgstr "Неправильный формат команды (нативная версия)"
|
||||||
|
|
||||||
@ -497,12 +619,6 @@ msgstr "Неправильный формат команды (flatpak)"
|
|||||||
msgid "File not found: {0}"
|
msgid "File not found: {0}"
|
||||||
msgstr "Файл не найден: {0}"
|
msgstr "Файл не найден: {0}"
|
||||||
|
|
||||||
msgid "Cannot launch game while another game is running"
|
|
||||||
msgstr "Невозможно запустить игру пока запущена другая"
|
|
||||||
|
|
||||||
msgid "Launching"
|
|
||||||
msgstr "Идёт запуск"
|
|
||||||
|
|
||||||
msgid "Reboot"
|
msgid "Reboot"
|
||||||
msgstr "Перезагрузить"
|
msgstr "Перезагрузить"
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ from portprotonqt.system_overlay import SystemOverlay
|
|||||||
|
|
||||||
from portprotonqt.image_utils import load_pixmap_async, round_corners, ImageCarousel
|
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.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.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.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 (
|
from portprotonqt.config_utils import (
|
||||||
@ -40,6 +40,7 @@ from typing import cast
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from PySide6.QtWidgets import QSizePolicy
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@ -52,38 +53,6 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.setAcceptDrops(True)
|
|
||||||
self.current_exec_line = None
|
|
||||||
self.currentDetailPage = None
|
|
||||||
self.current_play_button = None
|
|
||||||
self.pending_games = []
|
|
||||||
self.game_card_cache = {}
|
|
||||||
self.pending_images = {}
|
|
||||||
self.total_games = 0
|
|
||||||
self.games_load_timer = QTimer(self)
|
|
||||||
self.games_load_timer.setSingleShot(True)
|
|
||||||
self.games_load_timer.timeout.connect(self.finalize_game_loading)
|
|
||||||
self.games_loaded.connect(self.on_games_loaded)
|
|
||||||
self.current_add_game_dialog = None
|
|
||||||
|
|
||||||
# Добавляем таймер для дебаунсинга сохранения настроек
|
|
||||||
self.settingsDebounceTimer = QTimer(self)
|
|
||||||
self.settingsDebounceTimer.setSingleShot(True)
|
|
||||||
self.settingsDebounceTimer.setInterval(300) # 300 мс задержка
|
|
||||||
self.settingsDebounceTimer.timeout.connect(self.applySettingsDelayed)
|
|
||||||
|
|
||||||
read_time_config()
|
|
||||||
# Set LEGENDARY_CONFIG_PATH to ~/.cache/PortProtonQt/legendary_cache
|
|
||||||
self.legendary_config_path = os.path.join(
|
|
||||||
os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")),
|
|
||||||
"PortProtonQt", "legendary_cache"
|
|
||||||
)
|
|
||||||
os.makedirs(self.legendary_config_path, exist_ok=True)
|
|
||||||
os.environ["LEGENDARY_CONFIG_PATH"] = self.legendary_config_path
|
|
||||||
|
|
||||||
self.legendary_path = os.path.join(self.legendary_config_path, "legendary")
|
|
||||||
self.downloader = Downloader(max_workers=4)
|
|
||||||
|
|
||||||
# Создаём менеджер тем и читаем, какая тема выбрана
|
# Создаём менеджер тем и читаем, какая тема выбрана
|
||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
selected_theme = read_theme_from_config()
|
selected_theme = read_theme_from_config()
|
||||||
@ -116,9 +85,47 @@ class MainWindow(QMainWindow):
|
|||||||
self.updateGameGrid
|
self.updateGameGrid
|
||||||
)
|
)
|
||||||
|
|
||||||
|
QApplication.setStyle("Fusion")
|
||||||
|
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE)
|
||||||
|
self.setAcceptDrops(True)
|
||||||
|
self.current_exec_line = None
|
||||||
|
self.currentDetailPage = None
|
||||||
|
self.current_play_button = None
|
||||||
|
self.current_focused_card = None
|
||||||
|
self.pending_games = []
|
||||||
|
self.game_card_cache = {}
|
||||||
|
self.pending_images = {}
|
||||||
|
self.total_games = 0
|
||||||
|
self.games_load_timer = QTimer(self)
|
||||||
|
self.games_load_timer.setSingleShot(True)
|
||||||
|
self.games_load_timer.timeout.connect(self.finalize_game_loading)
|
||||||
|
self.games_loaded.connect(self.on_games_loaded)
|
||||||
|
self.current_add_game_dialog = None
|
||||||
|
self.current_hovered_card = None
|
||||||
|
|
||||||
|
# Добавляем таймер для дебаунсинга сохранения настроек
|
||||||
|
self.settingsDebounceTimer = QTimer(self)
|
||||||
|
self.settingsDebounceTimer.setSingleShot(True)
|
||||||
|
self.settingsDebounceTimer.setInterval(300) # 300 мс задержка
|
||||||
|
self.settingsDebounceTimer.timeout.connect(self.applySettingsDelayed)
|
||||||
|
|
||||||
|
read_time_config()
|
||||||
|
# Set LEGENDARY_CONFIG_PATH to ~/.cache/PortProtonQt/legendary_cache
|
||||||
|
self.legendary_config_path = os.path.join(
|
||||||
|
os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")),
|
||||||
|
"PortProtonQt", "legendary_cache"
|
||||||
|
)
|
||||||
|
os.makedirs(self.legendary_config_path, exist_ok=True)
|
||||||
|
os.environ["LEGENDARY_CONFIG_PATH"] = self.legendary_config_path
|
||||||
|
|
||||||
|
self.legendary_path = os.path.join(self.legendary_config_path, "legendary")
|
||||||
|
self.downloader = Downloader(max_workers=4)
|
||||||
|
|
||||||
# Статус-бар
|
# Статус-бар
|
||||||
self.setStatusBar(QStatusBar(self))
|
self.setStatusBar(QStatusBar(self))
|
||||||
|
self.statusBar().setStyleSheet(self.theme.STATUS_BAR_STYLE)
|
||||||
self.progress_bar = QProgressBar()
|
self.progress_bar = QProgressBar()
|
||||||
|
self.progress_bar.setStyleSheet(self.theme.PROGRESS_BAR_STYLE)
|
||||||
self.progress_bar.setMaximumWidth(200)
|
self.progress_bar.setMaximumWidth(200)
|
||||||
self.progress_bar.setTextVisible(True)
|
self.progress_bar.setTextVisible(True)
|
||||||
self.progress_bar.setVisible(False)
|
self.progress_bar.setVisible(False)
|
||||||
@ -199,8 +206,6 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
self.restore_state()
|
self.restore_state()
|
||||||
|
|
||||||
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE)
|
|
||||||
self.setStyleSheet(self.theme.MESSAGE_BOX_STYLE)
|
|
||||||
self.input_manager = InputManager(self)
|
self.input_manager = InputManager(self)
|
||||||
QTimer.singleShot(0, self.loadGames)
|
QTimer.singleShot(0, self.loadGames)
|
||||||
|
|
||||||
@ -241,6 +246,65 @@ class MainWindow(QMainWindow):
|
|||||||
self.updateGameGrid()
|
self.updateGameGrid()
|
||||||
self.progress_bar.setVisible(False)
|
self.progress_bar.setVisible(False)
|
||||||
|
|
||||||
|
def _on_card_focused(self, game_name: str, is_focused: bool):
|
||||||
|
"""Обработчик сигнала focusChanged от GameCard."""
|
||||||
|
card_key = None
|
||||||
|
for key, card in self.game_card_cache.items():
|
||||||
|
if card.name == game_name:
|
||||||
|
card_key = key
|
||||||
|
break
|
||||||
|
|
||||||
|
if not card_key:
|
||||||
|
return
|
||||||
|
|
||||||
|
card = self.game_card_cache[card_key]
|
||||||
|
|
||||||
|
if is_focused:
|
||||||
|
# Если карточка получила фокус
|
||||||
|
if self.current_hovered_card and self.current_hovered_card != card:
|
||||||
|
# Сбрасываем текущую hovered карточку
|
||||||
|
self.current_hovered_card._hovered = False
|
||||||
|
self.current_hovered_card.leaveEvent(None)
|
||||||
|
self.current_hovered_card = None
|
||||||
|
if self.current_focused_card and self.current_focused_card != card:
|
||||||
|
# Сбрасываем текущую focused карточку
|
||||||
|
self.current_focused_card._focused = False
|
||||||
|
self.current_focused_card.clearFocus()
|
||||||
|
self.current_focused_card = card
|
||||||
|
else:
|
||||||
|
# Если карточка потеряла фокус
|
||||||
|
if self.current_focused_card == card:
|
||||||
|
self.current_focused_card = None
|
||||||
|
|
||||||
|
def _on_card_hovered(self, game_name: str, is_hovered: bool):
|
||||||
|
"""Обработчик сигнала hoverChanged от GameCard."""
|
||||||
|
card_key = None
|
||||||
|
for key, card in self.game_card_cache.items():
|
||||||
|
if card.name == game_name:
|
||||||
|
card_key = key
|
||||||
|
break
|
||||||
|
|
||||||
|
if not card_key:
|
||||||
|
return
|
||||||
|
|
||||||
|
card = self.game_card_cache[card_key]
|
||||||
|
|
||||||
|
if is_hovered:
|
||||||
|
# Если мышь наведена на карточку
|
||||||
|
if self.current_focused_card and self.current_focused_card != card:
|
||||||
|
# Сбрасываем текущую focused карточку
|
||||||
|
self.current_focused_card._focused = False
|
||||||
|
self.current_focused_card.clearFocus()
|
||||||
|
if self.current_hovered_card and self.current_hovered_card != card:
|
||||||
|
# Сбрасываем предыдущую hovered карточку
|
||||||
|
self.current_hovered_card._hovered = False
|
||||||
|
self.current_hovered_card.leaveEvent(None)
|
||||||
|
self.current_hovered_card = card
|
||||||
|
else:
|
||||||
|
# Если мышь покинула карточку
|
||||||
|
if self.current_hovered_card == card:
|
||||||
|
self.current_hovered_card = None
|
||||||
|
|
||||||
def loadGames(self):
|
def loadGames(self):
|
||||||
display_filter = read_display_filter()
|
display_filter = read_display_filter()
|
||||||
favorites = read_favorites()
|
favorites = read_favorites()
|
||||||
@ -261,19 +325,25 @@ class MainWindow(QMainWindow):
|
|||||||
self.update_status_message.emit
|
self.update_status_message.emit
|
||||||
)
|
)
|
||||||
elif display_filter == "favorites":
|
elif display_filter == "favorites":
|
||||||
def on_all_games(portproton_games, steam_games):
|
def on_all_games(portproton_games, steam_games, epic_games):
|
||||||
games = [game for game in portproton_games + steam_games if game[0] in favorites]
|
games = [game for game in portproton_games + steam_games + epic_games if game[0] in favorites]
|
||||||
self.games_loaded.emit(games)
|
self.games_loaded.emit(games)
|
||||||
self._load_portproton_games_async(
|
self._load_portproton_games_async(
|
||||||
lambda pg: self._load_steam_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:
|
else:
|
||||||
def on_all_games(portproton_games, steam_games):
|
def on_all_games(portproton_games, steam_games, epic_games):
|
||||||
seen = set()
|
seen = set()
|
||||||
games = []
|
games = []
|
||||||
for game in portproton_games + steam_games:
|
for game in portproton_games + steam_games + epic_games:
|
||||||
# Уникальный ключ: имя + exec_line
|
# Уникальный ключ: имя + exec_line
|
||||||
key = (game[0], game[4])
|
key = (game[0], game[4])
|
||||||
if key not in seen:
|
if key not in seen:
|
||||||
@ -282,7 +352,13 @@ class MainWindow(QMainWindow):
|
|||||||
self.games_loaded.emit(games)
|
self.games_loaded.emit(games)
|
||||||
self._load_portproton_games_async(
|
self._load_portproton_games_async(
|
||||||
lambda pg: self._load_steam_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 []
|
return []
|
||||||
@ -681,6 +757,8 @@ class MainWindow(QMainWindow):
|
|||||||
card_width=self.card_width,
|
card_width=self.card_width,
|
||||||
context_menu_manager=self.context_menu_manager
|
context_menu_manager=self.context_menu_manager
|
||||||
)
|
)
|
||||||
|
card.hoverChanged.connect(self._on_card_hovered)
|
||||||
|
card.focusChanged.connect(self._on_card_focused)
|
||||||
# Подключаем сигналы контекстного меню
|
# Подключаем сигналы контекстного меню
|
||||||
card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut)
|
card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut)
|
||||||
card.deleteGameRequested.connect(self.context_menu_manager.delete_game)
|
card.deleteGameRequested.connect(self.context_menu_manager.delete_game)
|
||||||
@ -890,9 +968,11 @@ class MainWindow(QMainWindow):
|
|||||||
formLayout = QFormLayout()
|
formLayout = QFormLayout()
|
||||||
formLayout.setContentsMargins(0, 10, 0, 0)
|
formLayout.setContentsMargins(0, 10, 0, 0)
|
||||||
formLayout.setSpacing(10)
|
formLayout.setSpacing(10)
|
||||||
|
formLayout.setLabelAlignment(Qt.AlignmentFlag.AlignLeft)
|
||||||
|
|
||||||
# 1. Time detail_level
|
# 1. Time detail_level
|
||||||
self.timeDetailCombo = QComboBox()
|
self.timeDetailCombo = QComboBox()
|
||||||
|
self.timeDetailCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.time_keys = ["detailed", "brief"]
|
self.time_keys = ["detailed", "brief"]
|
||||||
self.time_labels = [_("detailed"), _("brief")]
|
self.time_labels = [_("detailed"), _("brief")]
|
||||||
self.timeDetailCombo.addItems(self.time_labels)
|
self.timeDetailCombo.addItems(self.time_labels)
|
||||||
@ -911,6 +991,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 2. Games sort_method
|
# 2. Games sort_method
|
||||||
self.gamesSortCombo = QComboBox()
|
self.gamesSortCombo = QComboBox()
|
||||||
|
self.gamesSortCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.sort_keys = ["last_launch", "playtime", "alphabetical", "favorites"]
|
self.sort_keys = ["last_launch", "playtime", "alphabetical", "favorites"]
|
||||||
self.sort_labels = [_("last launch"), _("playtime"), _("alphabetical"), _("favorites")]
|
self.sort_labels = [_("last launch"), _("playtime"), _("alphabetical"), _("favorites")]
|
||||||
self.gamesSortCombo.addItems(self.sort_labels)
|
self.gamesSortCombo.addItems(self.sort_labels)
|
||||||
@ -929,8 +1010,9 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
# 3. Games display_filter
|
# 3. Games display_filter
|
||||||
self.filter_keys = ["all", "steam", "portproton", "favorites", "epic"]
|
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 = QComboBox()
|
||||||
|
self.gamesDisplayCombo.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
self.gamesDisplayCombo.addItems(self.filter_labels)
|
self.gamesDisplayCombo.addItems(self.filter_labels)
|
||||||
self.gamesDisplayCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
|
self.gamesDisplayCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
|
||||||
self.gamesDisplayCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
self.gamesDisplayCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
@ -1011,6 +1093,37 @@ class MainWindow(QMainWindow):
|
|||||||
self.gamepadRumbleCheckBox.setChecked(current_rumble_state)
|
self.gamepadRumbleCheckBox.setChecked(current_rumble_state)
|
||||||
formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox)
|
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)
|
layout.addLayout(formLayout)
|
||||||
|
|
||||||
# Кнопки
|
# Кнопки
|
||||||
@ -1061,6 +1174,37 @@ class MainWindow(QMainWindow):
|
|||||||
logger.error(f"Failed to open Legendary login page: {e}")
|
logger.error(f"Failed to open Legendary login page: {e}")
|
||||||
self.statusBar().showMessage(_("Failed to open Legendary login page"), 3000)
|
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):
|
def resetSettings(self):
|
||||||
"""Сбрасывает настройки и перезапускает приложение."""
|
"""Сбрасывает настройки и перезапускает приложение."""
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
@ -1456,7 +1600,7 @@ class MainWindow(QMainWindow):
|
|||||||
steamLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://steamcommunity.com/app/{appid}")))
|
steamLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://steamcommunity.com/app/{appid}")))
|
||||||
|
|
||||||
# Epic Games Store бейдж
|
# Epic Games Store бейдж
|
||||||
egs_icon = self.theme_manager.get_icon("steam")
|
egs_icon = self.theme_manager.get_icon("epic_games")
|
||||||
egsLabel = ClickableLabel(
|
egsLabel = ClickableLabel(
|
||||||
"Epic Games",
|
"Epic Games",
|
||||||
icon=egs_icon,
|
icon=egs_icon,
|
||||||
@ -1753,11 +1897,110 @@ class MainWindow(QMainWindow):
|
|||||||
self.target_exe = None
|
self.target_exe = None
|
||||||
|
|
||||||
def toggleGame(self, exec_line, button=None):
|
def toggleGame(self, exec_line, button=None):
|
||||||
|
# Обработка Steam-игр
|
||||||
if exec_line.startswith("steam://"):
|
if exec_line.startswith("steam://"):
|
||||||
url = QUrl(exec_line)
|
url = QUrl(exec_line)
|
||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
||||||
return
|
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)
|
entry_exec_split = shlex.split(exec_line)
|
||||||
if entry_exec_split[0] == "env":
|
if entry_exec_split[0] == "env":
|
||||||
if len(entry_exec_split) < 3:
|
if len(entry_exec_split) < 3:
|
||||||
@ -1771,18 +2014,20 @@ class MainWindow(QMainWindow):
|
|||||||
file_to_check = entry_exec_split[3]
|
file_to_check = entry_exec_split[3]
|
||||||
else:
|
else:
|
||||||
file_to_check = entry_exec_split[0]
|
file_to_check = entry_exec_split[0]
|
||||||
|
|
||||||
if not os.path.exists(file_to_check):
|
if not os.path.exists(file_to_check):
|
||||||
QMessageBox.warning(self, _("Error"), _("File not found: {0}").format(file_to_check))
|
QMessageBox.warning(self, _("Error"), _("File not found: {0}").format(file_to_check))
|
||||||
return
|
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:
|
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"))
|
QMessageBox.warning(self, _("Error"), _("Cannot launch game while another game is running"))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Обновляем кнопку
|
||||||
update_button = button if button is not None else self.current_play_button
|
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 self.game_processes and self.target_exe == current_exe:
|
||||||
if hasattr(self, 'input_manager'):
|
if hasattr(self, 'input_manager'):
|
||||||
self.input_manager.enable_gamepad_handling()
|
self.input_manager.enable_gamepad_handling()
|
||||||
@ -1835,6 +2080,15 @@ class MainWindow(QMainWindow):
|
|||||||
env_vars['START_FROM_STEAM'] = '1'
|
env_vars['START_FROM_STEAM'] = '1'
|
||||||
elif entry_exec_split[0] == "flatpak":
|
elif entry_exec_split[0] == "flatpak":
|
||||||
env_vars['START_FROM_STEAM'] = '1'
|
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)
|
process = subprocess.Popen(entry_exec_split, env=env_vars, shell=False, preexec_fn=os.setsid)
|
||||||
self.game_processes.append(process)
|
self.game_processes.append(process)
|
||||||
save_last_launch(exe_name, datetime.now())
|
save_last_launch(exe_name, datetime.now())
|
||||||
@ -1850,10 +2104,12 @@ class MainWindow(QMainWindow):
|
|||||||
self.checkProcessTimer = QTimer(self)
|
self.checkProcessTimer = QTimer(self)
|
||||||
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
||||||
self.checkProcessTimer.start(500)
|
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):
|
def closeEvent(self, event):
|
||||||
"""Завершает все дочерние процессы и сохраняет настройки при закрытии окна."""
|
"""Завершает все дочерние процессы и сохраняет настройки при закрытии окна."""
|
||||||
# Завершаем все игровые процессы
|
|
||||||
for proc in self.game_processes:
|
for proc in self.game_processes:
|
||||||
try:
|
try:
|
||||||
parent = psutil.Process(proc.pid)
|
parent = psutil.Process(proc.pid)
|
||||||
@ -1894,4 +2150,5 @@ class MainWindow(QMainWindow):
|
|||||||
self.checkProcessTimer.deleteLater()
|
self.checkProcessTimer.deleteLater()
|
||||||
self.checkProcessTimer = None
|
self.checkProcessTimer = None
|
||||||
|
|
||||||
|
QApplication.quit()
|
||||||
event.accept()
|
event.accept()
|
||||||
|
@ -20,8 +20,12 @@ class SystemOverlay(QDialog):
|
|||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
self.setStyleSheet(self.theme.OVERLAY_WINDOW_STYLE)
|
self.setStyleSheet(self.theme.OVERLAY_WINDOW_STYLE)
|
||||||
|
|
||||||
# Убираем рамку окна
|
# Make window stay on top and frameless
|
||||||
self.setWindowFlags(Qt.WindowType.FramelessWindowHint | Qt.WindowType.Dialog)
|
self.setWindowFlags(
|
||||||
|
Qt.WindowType.FramelessWindowHint |
|
||||||
|
Qt.WindowType.Dialog |
|
||||||
|
Qt.WindowType.WindowStaysOnTopHint
|
||||||
|
)
|
||||||
|
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
layout.setContentsMargins(20, 20, 20, 20)
|
layout.setContentsMargins(20, 20, 20, 20)
|
||||||
|
@ -8,6 +8,76 @@ current_theme_name = read_theme_from_config()
|
|||||||
favoriteLabelSize = 48, 48
|
favoriteLabelSize = 48, 48
|
||||||
pixmapsScaledSize = 60, 60
|
pixmapsScaledSize = 60, 60
|
||||||
|
|
||||||
|
GAME_CARD_ANIMATION = {
|
||||||
|
# Ширина обводки карточки в состоянии покоя (без наведения или фокуса).
|
||||||
|
# Влияет на толщину рамки вокруг карточки, когда она не выделена.
|
||||||
|
# Значение в пикселях.
|
||||||
|
"default_border_width": 2,
|
||||||
|
|
||||||
|
# Ширина обводки при наведении курсора.
|
||||||
|
# Увеличивает толщину рамки, когда курсор находится над карточкой.
|
||||||
|
# Значение в пикселях.
|
||||||
|
"hover_border_width": 8,
|
||||||
|
|
||||||
|
# Ширина обводки при фокусе (например, при выборе с клавиатуры).
|
||||||
|
# Увеличивает толщину рамки, когда карточка в фокусе.
|
||||||
|
# Значение в пикселях.
|
||||||
|
"focus_border_width": 12,
|
||||||
|
|
||||||
|
# Минимальная ширина обводки во время пульсирующей анимации.
|
||||||
|
# Определяет минимальную толщину рамки при пульсации (анимация "дыхания").
|
||||||
|
# Значение в пикселях.
|
||||||
|
"pulse_min_border_width": 8,
|
||||||
|
|
||||||
|
# Максимальная ширина обводки во время пульсирующей анимации.
|
||||||
|
# Определяет максимальную толщину рамки при пульсации.
|
||||||
|
# Значение в пикселях.
|
||||||
|
"pulse_max_border_width": 10,
|
||||||
|
|
||||||
|
# Длительность анимации изменения толщины обводки (например, при наведении или фокусе).
|
||||||
|
# Влияет на скорость перехода от одной ширины обводки к другой.
|
||||||
|
# Значение в миллисекундах.
|
||||||
|
"thickness_anim_duration": 300,
|
||||||
|
|
||||||
|
# Длительность одного цикла пульсирующей анимации.
|
||||||
|
# Определяет, как быстро рамка "пульсирует" между min и max значениями.
|
||||||
|
# Значение в миллисекундах.
|
||||||
|
"pulse_anim_duration": 800,
|
||||||
|
|
||||||
|
# Длительность анимации вращения градиента.
|
||||||
|
# Влияет на скорость, с которой градиентная обводка вращается вокруг карточки.
|
||||||
|
# Значение в миллисекундах.
|
||||||
|
"gradient_anim_duration": 3000,
|
||||||
|
|
||||||
|
# Начальный угол градиента (в градусах).
|
||||||
|
# Определяет начальную точку вращения градиента при старте анимации.
|
||||||
|
"gradient_start_angle": 360,
|
||||||
|
|
||||||
|
# Конечный угол градиента (в градусах).
|
||||||
|
# Определяет конечную точку вращения градиента.
|
||||||
|
# Значение 0 означает полный поворот на 360 градусов.
|
||||||
|
"gradient_end_angle": 0,
|
||||||
|
|
||||||
|
# Тип кривой сглаживания для анимации увеличения обводки (при наведении/фокусе).
|
||||||
|
# Влияет на "чувство" анимации (например, плавное ускорение или замедление).
|
||||||
|
# Возможные значения: строки, соответствующие QEasingCurve.Type (например, "OutBack", "InOutQuad").
|
||||||
|
"thickness_easing_curve": "OutBack",
|
||||||
|
|
||||||
|
# Тип кривой сглаживания для анимации уменьшения обводки (при уходе курсора/потере фокуса).
|
||||||
|
# Влияет на "чувство" возврата к исходной ширине обводки.
|
||||||
|
"thickness_easing_curve_out": "InBack",
|
||||||
|
|
||||||
|
# Цвета градиента для анимированной обводки.
|
||||||
|
# Список словарей, где каждый словарь задает позицию (0.0–1.0) и цвет в формате hex.
|
||||||
|
# Влияет на внешний вид обводки при наведении или фокусе.
|
||||||
|
"gradient_colors": [
|
||||||
|
{"position": 0, "color": "#00fff5"}, # Начальный цвет (циан)
|
||||||
|
{"position": 0.33, "color": "#FF5733"}, # Цвет на 33% (оранжевый)
|
||||||
|
{"position": 0.66, "color": "#9B59B6"}, # Цвет на 66% (пурпурный)
|
||||||
|
{"position": 1, "color": "#00fff5"} # Конечный цвет (возвращение к циану)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
# СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА
|
# СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА
|
||||||
MAIN_WINDOW_HEADER_STYLE = """
|
MAIN_WINDOW_HEADER_STYLE = """
|
||||||
QFrame {
|
QFrame {
|
||||||
|
File diff suppressed because it is too large
Load Diff
26
renovate.json
Normal file
26
renovate.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": ["config:best-practices"],
|
||||||
|
"rebaseWhen": "never",
|
||||||
|
"lockFileMaintenance": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
"packageRules": [
|
||||||
|
{
|
||||||
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
|
"automerge": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"automerge": true,
|
||||||
|
"matchUpdateTypes": ["pin", "pinDigest"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"matchFileNames": [".gitea/workflows/**.yaml", ".gitea/workflows/**.yml"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": false,
|
||||||
|
"matchFileNames": [".python-version"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Reference in New Issue
Block a user