Compare commits
108 Commits
ba9d8b76d8
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
0231073b19
|
|||
|
dec24429f5
|
|||
|
4a758f3b3c
|
|||
|
0853dd1579
|
|||
|
bbb87c0455
|
|||
|
b32a71a125
|
|||
|
|
bddf9f850a | ||
|
|
a9c3cfa167 | ||
|
7675bc4cdc
|
|||
|
ffa203f019
|
|||
|
3eed25ecee
|
|||
|
3736bb279e
|
|||
|
|
b59ee5ae8e | ||
|
33176590fd
|
|||
|
8046065929
|
|||
|
|
fbad5add6c | ||
|
438e9737ea
|
|||
|
2d39a4c740
|
|||
|
567203b0b0
|
|||
|
502cbc5030
|
|||
|
9b61215152
|
|||
|
10d3fe8ab4
|
|||
|
a568ad9ef8
|
|||
|
f074843fc8
|
|||
|
4ab078b93e
|
|||
|
7df6ad3b80
|
|||
|
464ad0fe9c
|
|||
|
cde92885d4
|
|||
|
120c7b319c
|
|||
|
596aed0077
|
|||
|
6fc6cb1e02
|
|||
|
186e28a19b
|
|||
|
28e4d1e77c
|
|||
|
fff1f888c4
|
|||
|
fdd5a0a3d5
|
|||
|
792e52d981
|
|||
|
84d5e46a74
|
|||
|
4bc764d568
|
|||
|
9a18aa037e
|
|||
|
ed62d2d1c4
|
|||
|
accc9b18b6
|
|||
|
82249d7eab
|
|||
|
476c896940
|
|||
|
b1047ba18e
|
|||
|
987199d8e6
|
|||
|
|
ef1acd4581 | ||
|
96f884904c
|
|||
|
b856a2afae
|
|||
|
55ef0030e6
|
|||
|
8aaeaa4824
|
|||
|
f55372b480
|
|||
|
4d6f32f053
|
|||
|
a2f5141b20
|
|||
|
e3cb2857e7
|
|||
|
efe8a35832
|
|||
|
61fae97dad
|
|||
|
5442100f64
|
|||
|
2d6ef84798
|
|||
|
|
f4aee15b5d | ||
|
87a65108a5
|
|||
|
bb617708ac
|
|||
|
1cf332cd87
|
|||
|
577ad4d3a3
|
|||
|
ef3f2d6e96
|
|||
|
657d7728a6
|
|||
| 9452bfda2e | |||
| 7eb2db0d68 | |||
| 6ef7a03366 | |||
| e5af354b56 | |||
| e6e5f6c8ea | |||
| 84306bb31b | |||
| 60af4d1482 | |||
| 692e11b21d | |||
| b1a804811e | |||
| 9a30cfaea7 | |||
| 5dd2f71f5e | |||
|
dba172361b
|
|||
|
a9c70b8818
|
|||
|
135ace732f
|
|||
|
8b727f64e1
|
|||
|
a8eb591da5
|
|||
|
fe4ca1ee87
|
|||
|
ffe3e9d3d6
|
|||
|
49d39b5d61
|
|||
|
|
03566da704
|
||
|
|
7f996ab6a0 | ||
|
|
9e17978155 | ||
|
5d0185b1b4
|
|||
|
5c134be04e
|
|||
|
8c66695192
|
|||
|
7a141d8e46
|
|||
|
abb2377fb7
|
|||
|
75f4f346de
|
|||
|
87a9f85272
|
|||
|
240f685ece
|
|||
|
af4e3e95bb
|
|||
|
017d9a42cf
|
|||
|
18b7c4054b
|
|||
|
dd7f71b70a
|
|||
|
8fd44c575b
|
|||
|
65b43c1572
|
|||
|
f35276abfe
|
|||
|
6fea9a9a7e
|
|||
|
5189474631
|
|||
|
|
416cc6a268 | ||
|
|
3b44ed5252 | ||
|
c8c45dda06
|
|||
|
3f9f794e6f
|
@@ -94,7 +94,7 @@ jobs:
|
||||
name: Build Arch Package
|
||||
runs-on: ubuntu-22.04
|
||||
container:
|
||||
image: archlinux:base-devel@sha256:0589aa8f31d8f64c630a2d1cc0b4c3847a1a63c988abd63d78d3c9bd94764f64
|
||||
image: archlinux:base-devel@sha256:87a967f07ba6319fc35c8c4e6ce6acdb4343b57aa817398a5d2db57bd8edc731
|
||||
volumes:
|
||||
- /usr:/usr-host
|
||||
- /opt:/opt-host
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
# Common version, will be used for tagging the release
|
||||
VERSION: 0.1.6
|
||||
VERSION: 0.1.8
|
||||
PKGDEST: "/tmp/portprotonqt"
|
||||
PACKAGE: "portprotonqt"
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
@@ -180,10 +180,12 @@ jobs:
|
||||
|
||||
- name: Release
|
||||
uses: https://gitea.com/actions/gitea-release-action@v1
|
||||
env:
|
||||
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
|
||||
with:
|
||||
body_path: changelog.txt
|
||||
token: ${{ env.GITEA_TOKEN }}
|
||||
tag_name: v${{ env.VERSION }}
|
||||
prerelease: true
|
||||
files: release/**/*
|
||||
sha256sum: true
|
||||
sha256sum: false
|
||||
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
needs: changes
|
||||
if: needs.changes.outputs.arch == 'true' || github.event_name == 'workflow_dispatch'
|
||||
container:
|
||||
image: archlinux:base-devel@sha256:0589aa8f31d8f64c630a2d1cc0b4c3847a1a63c988abd63d78d3c9bd94764f64
|
||||
image: archlinux:base-devel@sha256:87a967f07ba6319fc35c8c4e6ce6acdb4343b57aa817398a5d2db57bd8edc731
|
||||
volumes:
|
||||
- /usr:/usr-host
|
||||
- /opt:/opt-host
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/renovatebot/renovate:latest@sha256:dd5721b9a686a40d81687643e4b71b82a0ca31fb653fd727538af69104fd388d
|
||||
container: ghcr.io/renovatebot/renovate:latest@sha256:17c8966ef38fc361e108a550ffe2dcedf73e846f9975a974aea3d48c66b107a6
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ repos:
|
||||
- id: check-yaml
|
||||
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.8.22
|
||||
rev: 0.9.5
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.13.2
|
||||
rev: v0.14.2
|
||||
hooks:
|
||||
- id: ruff-check
|
||||
|
||||
|
||||
42
CHANGELOG.md
@@ -3,20 +3,59 @@
|
||||
Все заметные изменения в этом проекте фиксируются в этом файле.
|
||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
## [0.1.8] - 2025-10-18
|
||||
|
||||
### Added
|
||||
- В настройки добавлен пункт для выбора типа геймпада для подсказок по управлению
|
||||
- В настройки добавлен пункт для выбора сворачивать ли приложение в трей или нет
|
||||
- К диалогу добавления игры, Winetricks, диалогу выбора файлов и виртуальной клавиатуре добавлены подсказки по управлению с геймпада
|
||||
- Во вкладку автоустановок добавлен слайдер изменения размера карточек (они со слайдером в библиотеке независимы)
|
||||
|
||||
### Changed
|
||||
- При завершении автоустановки приложение больше не перезапускается
|
||||
- Выбор exe в диалоге добавления игры больше не перезаписывает введенное в поле название
|
||||
- Обновлены и дополнены скриншоты темы
|
||||
|
||||
### Fixed
|
||||
- Исправлено наложение карточек при смене фильтра игр
|
||||
- Исправлена невозможность запуска приложения без подключёного геймпада
|
||||
- Исправлена невозможность установки компонентов Winetricks через геймпад
|
||||
- Ресиверы и виртуальные устройства больше не считаются за геймпад
|
||||
|
||||
|
||||
### Contributors
|
||||
- @Vector_null
|
||||
|
||||
---
|
||||
|
||||
## [0.1.7] - 2025-10-12
|
||||
|
||||
### Added
|
||||
- Возможность скроллинга библиотеки мышью или пальцем
|
||||
- Импорт и экспорт бекапа префикса
|
||||
- Диалог для управление Winetricks
|
||||
- Кнопки для удаления префикса, wine или proton
|
||||
- Все настройки Wine с оригинального PortProton
|
||||
- Виртуальная клавиатура в диалог добавления игры и поиск по библиотеке и автоустановках
|
||||
- Вкладка автоустановок
|
||||
- В заголовке окна теперь отображается версия приложения и хеш коммита если запуск идёт с гита
|
||||
|
||||
### Changed
|
||||
- Проведён рефакторинг и оптимизация всего что связано с карточками и библиотекой игр
|
||||
- В диалог выбора файлов в режиме directory_only (при выборе куда сохранить бекап префикса) добавлена кнопка ./ обозначающая нынешнюю папку
|
||||
|
||||
### Fixed
|
||||
- Исправлен вылет диалога выбора файлов при выборе обложки если в папке более сотни изображений
|
||||
- Исправлено зависание при добавлении или удалении игры в Wayland
|
||||
- Исправлено зависание при поиске игр
|
||||
- Исправлено ошибочное присвоение ID игры с названием "GAME", возникавшее, если исполняемый файл находился в подпапке `game/` (часто встречается у игр на Unity)
|
||||
- Исправлена ошибка из-за которой подсказки по управлению снизу и сверху могли не совпадать с друг другом, из-за чего возле вкладок были стрелки клавиатуры, а снизу кнопки геймпада
|
||||
- Исправлен выход из полноэкранного режима при отключении геймпада подключённого по USB даже если настройка "Режим полноэкранного отображения приложения при подключении геймпада" выключена
|
||||
- При сохранении настроек теперь не меняется размер окна
|
||||
|
||||
### Contributors
|
||||
- @wmigor (Igor Akulov)
|
||||
- @Vector_null
|
||||
|
||||
---
|
||||
|
||||
@@ -39,6 +78,7 @@
|
||||
|
||||
### Contributors
|
||||
- @wmigor (Igor Akulov)
|
||||
- @Vector_null
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -54,8 +54,6 @@ PortProtonQt использует код и зависимости от след
|
||||
- [Legendary](https://github.com/derrod/legendary) — инструмент для работы с Epic Games Store, лицензия [GPL-3.0](https://github.com/derrod/legendary/blob/master/LICENSE).
|
||||
- [Icoextract](https://github.com/jlu5/icoextract) — библиотека для извлечения иконок, лицензия [MIT](https://github.com/jlu5/icoextract/blob/master/LICENSE).
|
||||
- [HowLongToBeat Python API](https://github.com/ScrappyCocco/HowLongToBeat-PythonAPI) — библиотека для взаимодействия с HowLongToBeat, лицензия [MIT](https://github.com/ScrappyCocco/HowLongToBeat-PythonAPI/blob/master/LICENSE.md).
|
||||
- [Those Awesome Guys: Gamepad prompts images](https://thoseawesomeguys.com/prompts/) - Набор подсказок для геймпада и клавиатур, лицензия [CC0](https://creativecommons.org/public-domain/cc0/)
|
||||
|
||||
Полный текст лицензий см. в файле [LICENSE](LICENSE).
|
||||
|
||||
> [!WARNING]
|
||||
|
||||
15
TODO.md
@@ -1,6 +1,6 @@
|
||||
- [X] Адаптировать структуру проекта для поддержки инструментов сборки
|
||||
- [X] Добавить возможность управления с геймпада
|
||||
- [ ] Добавить возможность управления с тачскрина
|
||||
- [X] Добавить возможность управления с тачскрина (Формально и так есть)
|
||||
- [X] Добавить возможность управления с мыши и клавиатуры
|
||||
- [X] Добавить систему тем [Документация](documentation/theme_guide)
|
||||
- [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
|
||||
@@ -11,18 +11,18 @@
|
||||
- [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800)
|
||||
- [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
|
||||
- [X] Получать описания и названия игр из базы данных Steam
|
||||
- [X] Получать обложки для игр из SteamGridDB или CDN Steam
|
||||
- [X] Получать обложки для игр из CDN Steam
|
||||
- [X] Оптимизировать работу со Steam API для ускорения времени запуска
|
||||
- [X] Улучшить функцию поиска в Steam API для исправления некорректного определения ID (например, Graven определялся как ENGRAVEN или GRAVENFALL, Spore — как SporeBound или Spore Valley)
|
||||
- [ ] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода
|
||||
- [X] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода
|
||||
- [X] Решить проблему с ограничением Steam API в 50 тысяч игр за один запрос (иногда нужные игры не попадают в выборку и остаются без обложки)
|
||||
- [X] Избавиться от вызовов yad
|
||||
- [X] Реализовать собственный системный трей вместо использования трея PortProton
|
||||
- [X] Добавить экранную клавиатуру в поиск (реализация собственной клавиатуры слишком затратна, поэтому используется встроенная в DE клавиатура: Maliit в KDE, gjs-osk в GNOME, Squeekboard в Phosh, клавиатура SteamOS и т.д.)
|
||||
- [X] Добавить экранную клавиатуру в поиск
|
||||
- [X] Добавить сортировку карточек по различным критериям (доступны: по недавности, количеству наигранного времени, избранному или алфавиту)
|
||||
- [X] Добавить индикацию запуска приложения
|
||||
- [X] Достигнуть паритета функциональности с Ingame
|
||||
- [ ] Достигнуть паритета функциональности с PortProton
|
||||
- [ ] Достигнуть паритета функциональности с PortProton (остались настройки игр и обновление скриптов)
|
||||
- [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}`
|
||||
- [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/)
|
||||
- [X] Добавить переводы в переопределения
|
||||
@@ -49,7 +49,7 @@
|
||||
- [X] Добавить недокументированные параметры конфигурации в GUI (time_detail_level, games_sort_method, games_display_filter)
|
||||
- [X] Добавить систему избранного для карточек
|
||||
- [X] Заменить все `print` на `logging`
|
||||
- [ ] Привести все логи к единому языку
|
||||
- [X] Привести все логи к единому языку
|
||||
- [X] Уменьшить количество подстановок в переводах
|
||||
- [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog)
|
||||
- [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py`
|
||||
@@ -62,7 +62,6 @@
|
||||
- [X] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)
|
||||
- [X] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63))
|
||||
- [X] Скопировать логику управления с D-pad на стрелки с клавиатуры
|
||||
- [ ] Доделать светлую тему
|
||||
- [ ] Добавить подсказки к управлению с геймпада
|
||||
- [X] Добавить подсказки к управлению с геймпада
|
||||
- [X] Добавить миниатюры к выбору файлов в диалоге добавления игры
|
||||
- [X] Добавить быстрый доступ к смонтированным дискам к выбору файлов в диалоге добавления игры
|
||||
|
||||
@@ -36,7 +36,7 @@ AppDir:
|
||||
id: ru.linux_gaming.PortProtonQt
|
||||
name: PortProtonQt
|
||||
icon: ru.linux_gaming.PortProtonQt
|
||||
version: 0.1.6
|
||||
version: 0.1.8
|
||||
exec: usr/bin/python3
|
||||
exec_args: "-m portprotonqt.app $@"
|
||||
apt:
|
||||
@@ -54,6 +54,11 @@ AppDir:
|
||||
- libxcb-cursor0
|
||||
- libimage-exiftool-perl
|
||||
- xdg-utils
|
||||
- cabextract
|
||||
- curl
|
||||
- 7zip
|
||||
- unzip
|
||||
- unrar
|
||||
exclude:
|
||||
- "*-doc"
|
||||
- "*-man"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
pkgname=portprotonqt
|
||||
pkgver=0.1.6
|
||||
pkgver=0.1.8
|
||||
pkgrel=1
|
||||
pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store"
|
||||
arch=('any')
|
||||
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
|
||||
license=('GPL-3.0')
|
||||
depends=('python-numpy' 'python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client')
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'unrar')
|
||||
makedepends=('python-'{'build','installer','setuptools','wheel'})
|
||||
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt#tag=v$pkgver")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
@@ -6,7 +6,7 @@ arch=('any')
|
||||
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
|
||||
license=('GPL-3.0')
|
||||
depends=('python-numpy' 'python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client')
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'pyside6' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'unrar')
|
||||
makedepends=('python-'{'build','installer','setuptools','wheel'})
|
||||
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
@@ -46,6 +46,11 @@ Requires: python3-pillow
|
||||
Requires: perl-Image-ExifTool
|
||||
Requires: xdg-utils
|
||||
Requires: python3-beautifulsoup4
|
||||
Requires: cabextract
|
||||
Requires: gzip
|
||||
Requires: unzip
|
||||
Requires: curl
|
||||
Requires: unrar
|
||||
|
||||
%description -n python3-%{pypi_name}-git
|
||||
This application provides a sleek, intuitive graphical interface for managing and launching games from PortProton, Steam, and Epic Games Store. It consolidates your game libraries into a single, user-friendly hub for seamless navigation and organization. Its lightweight structure and cross-platform support deliver a cohesive gaming experience, eliminating the need for multiple launchers. Unique PortProton integration enhances Linux gaming, enabling effortless play of Windows-based titles with minimal setup.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%global pypi_name portprotonqt
|
||||
%global pypi_version 0.1.6
|
||||
%global pypi_version 0.1.8
|
||||
%global oname PortProtonQt
|
||||
%global _python_no_extras_requires 1
|
||||
|
||||
@@ -43,6 +43,11 @@ Requires: python3-pillow
|
||||
Requires: perl-Image-ExifTool
|
||||
Requires: xdg-utils
|
||||
Requires: python3-beautifulsoup4
|
||||
Requires: cabextract
|
||||
Requires: gzip
|
||||
Requires: unzip
|
||||
Requires: curl
|
||||
Requires: unrar
|
||||
|
||||
%description -n python3-%{pypi_name}
|
||||
This application provides a sleek, intuitive graphical interface for managing and launching games from PortProton, Steam, and Epic Games Store. It consolidates your game libraries into a single, user-friendly hub for seamless navigation and organization. Its lightweight structure and cross-platform support deliver a cohesive gaming experience, eliminating the need for multiple launchers. Unique PortProton integration enhances Linux gaming, enabling effortless play of Windows-based titles with minimal setup.
|
||||
|
||||
@@ -21,9 +21,9 @@ Current translation status:
|
||||
|
||||
| Locale | Progress | Translated |
|
||||
| :----- | -------: | ---------: |
|
||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 193 |
|
||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 193 |
|
||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 193 of 193 |
|
||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 249 |
|
||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 249 |
|
||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 249 of 249 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
| Локаль | Прогресс | Переведено |
|
||||
| :----- | -------: | ---------: |
|
||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 193 |
|
||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 193 |
|
||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 193 из 193 |
|
||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 249 |
|
||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 249 |
|
||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 249 из 249 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,17 +1,46 @@
|
||||
import sys
|
||||
from PySide6.QtCore import QLocale, QTranslator, QLibraryInfo
|
||||
import os
|
||||
import subprocess
|
||||
from PySide6.QtCore import QLocale, QTranslator, QLibraryInfo, QTimer, Qt
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||
|
||||
from portprotonqt.main_window import MainWindow
|
||||
from portprotonqt.config_utils import save_fullscreen_config
|
||||
from portprotonqt.config_utils import (
|
||||
save_fullscreen_config,
|
||||
read_fullscreen_config,
|
||||
get_portproton_start_command
|
||||
)
|
||||
from portprotonqt.logger import get_logger, setup_logger
|
||||
from portprotonqt.cli import parse_args
|
||||
|
||||
__app_id__ = "ru.linux_gaming.PortProtonQt"
|
||||
__app_name__ = "PortProtonQt"
|
||||
__app_version__ = "0.1.6"
|
||||
__app_version__ = "0.1.8"
|
||||
|
||||
def get_version():
|
||||
try:
|
||||
commit = subprocess.check_output(
|
||||
["git", "rev-parse", "--short", "HEAD"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
).decode("utf-8").strip()
|
||||
return f"{__app_version__} ({commit})"
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, OSError):
|
||||
return __app_version__
|
||||
|
||||
def main():
|
||||
os.environ["PW_CLI"] = "1"
|
||||
os.environ["PROCESS_LOG"] = "1"
|
||||
os.environ["START_FROM_STEAM"] = "1"
|
||||
|
||||
start_sh = get_portproton_start_command()
|
||||
|
||||
if start_sh is None:
|
||||
return
|
||||
|
||||
subprocess.run(start_sh + ["cli", "--initial"])
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setWindowIcon(QIcon.fromTheme(__app_id__))
|
||||
app.setDesktopFileName(__app_id__)
|
||||
@@ -19,40 +48,116 @@ def main():
|
||||
app.setApplicationVersion(__app_version__)
|
||||
|
||||
args = parse_args()
|
||||
|
||||
# Setup logger with specified debug level
|
||||
setup_logger(args.debug_level)
|
||||
|
||||
# Reinitialize logger after setup to ensure it uses the new configuration
|
||||
logger = get_logger(__name__)
|
||||
|
||||
# --- Single-instance logic ---
|
||||
server_name = __app_id__
|
||||
socket = QLocalSocket()
|
||||
socket.connectToServer(server_name)
|
||||
|
||||
if socket.waitForConnected(200):
|
||||
# Второй экземпляр — передаём команду первому
|
||||
fullscreen = args.fullscreen or read_fullscreen_config()
|
||||
msg = b"show:fullscreen" if fullscreen else b"show"
|
||||
socket.write(msg)
|
||||
socket.flush()
|
||||
socket.waitForBytesWritten(500)
|
||||
socket.disconnectFromServer()
|
||||
logger.info("Restored existing instance from tray")
|
||||
return
|
||||
|
||||
# Если старый сокет остался — удалить
|
||||
QLocalServer.removeServer(server_name)
|
||||
|
||||
local_server = QLocalServer()
|
||||
if not local_server.listen(server_name):
|
||||
logger.warning(f"Failed to start local server: {local_server.errorString()}")
|
||||
return
|
||||
|
||||
# --- Qt translations ---
|
||||
system_locale = QLocale.system()
|
||||
qt_translator = QTranslator()
|
||||
translations_path = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath)
|
||||
if qt_translator.load(system_locale, "qtbase", "_", translations_path):
|
||||
app.installTranslator(qt_translator)
|
||||
else:
|
||||
logger.warning(f"Qt translations for {system_locale.name()} not found in {translations_path}, using english language")
|
||||
logger.warning(
|
||||
f"Qt translations for {system_locale.name()} not found in {translations_path}, using English"
|
||||
)
|
||||
|
||||
window = MainWindow(app_name=__app_name__)
|
||||
# --- Main Window ---
|
||||
version = get_version()
|
||||
window = MainWindow(app_name=__app_name__, version=version)
|
||||
|
||||
if args.fullscreen:
|
||||
logger.info("Launching in fullscreen mode due to --fullscreen flag")
|
||||
# --- Handle incoming connections ---
|
||||
def handle_new_connection():
|
||||
conn = local_server.nextPendingConnection()
|
||||
if not conn:
|
||||
return
|
||||
|
||||
if conn.waitForReadyRead(1000):
|
||||
data = conn.readAll().data()
|
||||
msg = bytes(data).decode("utf-8", errors="ignore")
|
||||
logger.info(f"IPC message received: {msg}")
|
||||
|
||||
def restore_window():
|
||||
try:
|
||||
if msg.startswith("show"):
|
||||
if hasattr(window, "restore_from_tray"):
|
||||
window.restore_from_tray() # type: ignore[attr-defined]
|
||||
else:
|
||||
window.showNormal()
|
||||
window.raise_()
|
||||
window.activateWindow()
|
||||
window.setWindowState(
|
||||
window.windowState() & ~Qt.WindowState.WindowMinimized | Qt.WindowState.WindowActive
|
||||
)
|
||||
|
||||
if ":fullscreen" in msg:
|
||||
logger.info("Switching to fullscreen via IPC")
|
||||
save_fullscreen_config(True)
|
||||
window.showFullScreen()
|
||||
else:
|
||||
logger.info("Switching to normal window via IPC")
|
||||
save_fullscreen_config(False)
|
||||
window.showNormal()
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to restore window: {e}")
|
||||
|
||||
# Выполняем в основном потоке
|
||||
QTimer.singleShot(0, restore_window)
|
||||
|
||||
conn.disconnectFromServer()
|
||||
|
||||
local_server.newConnection.connect(handle_new_connection)
|
||||
|
||||
# --- Initial fullscreen state ---
|
||||
launch_fullscreen = args.fullscreen or read_fullscreen_config()
|
||||
if launch_fullscreen:
|
||||
logger.info(
|
||||
f"Launching in fullscreen mode ({'--fullscreen' if args.fullscreen else 'config'})"
|
||||
)
|
||||
save_fullscreen_config(True)
|
||||
window.showFullScreen()
|
||||
else:
|
||||
logger.info("Launching in normal mode")
|
||||
save_fullscreen_config(False)
|
||||
window.showNormal()
|
||||
|
||||
# --- Cleanup ---
|
||||
def cleanup_on_exit():
|
||||
nonlocal window
|
||||
app.aboutToQuit.disconnect()
|
||||
if window:
|
||||
window.close()
|
||||
app.quit()
|
||||
try:
|
||||
local_server.close()
|
||||
QLocalServer.removeServer(server_name)
|
||||
if window:
|
||||
window.close()
|
||||
except Exception as e:
|
||||
logger.warning(f"Cleanup error: {e}")
|
||||
|
||||
app.aboutToQuit.connect(cleanup_on_exit)
|
||||
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import os
|
||||
import configparser
|
||||
import shutil
|
||||
import subprocess
|
||||
from portprotonqt.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
_portproton_location = None
|
||||
_portproton_start_sh = None
|
||||
|
||||
# Paths to configuration files
|
||||
CONFIG_FILE = os.path.join(
|
||||
@@ -101,14 +103,14 @@ def read_file_content(file_path):
|
||||
return f.read().strip()
|
||||
|
||||
def get_portproton_location():
|
||||
"""Returns the path to the PortProton directory.
|
||||
Checks the cached path first. If not set, reads from PORTPROTON_CONFIG_FILE.
|
||||
If the path is invalid, uses the default directory.
|
||||
"""
|
||||
"""Возвращает путь к PortProton каталогу (строку) или None."""
|
||||
global _portproton_location
|
||||
|
||||
if _portproton_location is not None:
|
||||
return _portproton_location
|
||||
|
||||
location = None
|
||||
|
||||
if os.path.isfile(PORTPROTON_CONFIG_FILE):
|
||||
try:
|
||||
location = read_file_content(PORTPROTON_CONFIG_FILE).strip()
|
||||
@@ -116,19 +118,46 @@ def get_portproton_location():
|
||||
_portproton_location = location
|
||||
logger.info(f"PortProton path from configuration: {location}")
|
||||
return _portproton_location
|
||||
logger.warning(f"Invalid PortProton path in configuration: {location}, using default path")
|
||||
logger.warning(f"Invalid PortProton path in configuration: {location}, using defaults")
|
||||
except (OSError, PermissionError) as e:
|
||||
logger.warning(f"Failed to read PortProton configuration file: {e}, using default path")
|
||||
logger.warning(f"Failed to read PortProton configuration file: {e}")
|
||||
|
||||
default_dir = os.path.join(os.path.expanduser("~"), ".var", "app", "ru.linux_gaming.PortProton")
|
||||
if os.path.isdir(default_dir):
|
||||
_portproton_location = default_dir
|
||||
logger.info(f"Using flatpak PortProton directory: {default_dir}")
|
||||
default_flatpak_dir = os.path.join(os.path.expanduser("~"), ".var", "app", "ru.linux_gaming.PortProton")
|
||||
if os.path.isdir(default_flatpak_dir):
|
||||
_portproton_location = default_flatpak_dir
|
||||
logger.info(f"Using Flatpak PortProton directory: {default_flatpak_dir}")
|
||||
return _portproton_location
|
||||
|
||||
logger.warning("PortProton configuration and flatpak directory not found")
|
||||
logger.warning("PortProton configuration and Flatpak directory not found")
|
||||
return None
|
||||
|
||||
def get_portproton_start_command():
|
||||
"""Возвращает список команд для запуска PortProton (start.sh или flatpak run)."""
|
||||
portproton_path = get_portproton_location()
|
||||
if not portproton_path:
|
||||
return None
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["flatpak", "list"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False
|
||||
)
|
||||
if "ru.linux_gaming.PortProton" in result.stdout:
|
||||
logger.info("Detected Flatpak installation")
|
||||
return ["flatpak", "run", "ru.linux_gaming.PortProton"]
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
start_sh_path = os.path.join(portproton_path, "data", "scripts", "start.sh")
|
||||
if os.path.exists(start_sh_path):
|
||||
return [start_sh_path]
|
||||
|
||||
logger.warning("Neither flatpak nor start.sh found for PortProton")
|
||||
return None
|
||||
|
||||
|
||||
def parse_desktop_entry(file_path):
|
||||
"""Reads and parses a .desktop file using configparser.
|
||||
Returns None if the [Desktop Entry] section is missing.
|
||||
@@ -177,6 +206,26 @@ def save_card_size(card_width):
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
||||
cp.write(configfile)
|
||||
|
||||
def read_auto_card_size():
|
||||
"""Reads the card size (width) for Auto Install from the [Cards] section.
|
||||
Returns 250 if the parameter is not set.
|
||||
"""
|
||||
cp = read_config_safely(CONFIG_FILE)
|
||||
if cp is None or not cp.has_section("Cards") or not cp.has_option("Cards", "auto_card_width"):
|
||||
save_auto_card_size(250)
|
||||
return 250
|
||||
return cp.getint("Cards", "auto_card_width", fallback=250)
|
||||
|
||||
def save_auto_card_size(card_width):
|
||||
"""Saves the card size (width) for Auto Install to the [Cards] section."""
|
||||
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||
if "Cards" not in cp:
|
||||
cp["Cards"] = {}
|
||||
cp["Cards"]["auto_card_width"] = str(card_width)
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
||||
cp.write(configfile)
|
||||
|
||||
|
||||
def read_sort_method():
|
||||
"""Reads the sort method from the [Games] section.
|
||||
Returns 'last_launch' if the parameter is not set.
|
||||
@@ -259,6 +308,25 @@ def save_rumble_config(rumble_enabled):
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
||||
cp.write(configfile)
|
||||
|
||||
def read_gamepad_type():
|
||||
"""Reads the gamepad type from the [Gamepad] section.
|
||||
Returns 'xbox' if the parameter is missing.
|
||||
"""
|
||||
cp = read_config_safely(CONFIG_FILE)
|
||||
if cp is None or not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "type"):
|
||||
save_gamepad_type("xbox")
|
||||
return "xbox"
|
||||
return cp.get("Gamepad", "type", fallback="xbox").lower()
|
||||
|
||||
def save_gamepad_type(gpad_type):
|
||||
"""Saves the gamepad type to the [Gamepad] section."""
|
||||
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||
if "Gamepad" not in cp:
|
||||
cp["Gamepad"] = {}
|
||||
cp["Gamepad"]["type"] = gpad_type
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
||||
cp.write(configfile)
|
||||
|
||||
def ensure_default_proxy_config():
|
||||
"""Ensures the [Proxy] section exists in the configuration file.
|
||||
Creates it with empty values if missing.
|
||||
@@ -408,3 +476,22 @@ def save_favorite_folders(folders):
|
||||
cp["FavoritesFolders"]["folders"] = f'"{fav_str}"'
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
||||
cp.write(configfile)
|
||||
|
||||
def read_minimize_to_tray():
|
||||
"""Reads the minimize-to-tray setting from the [Display] section.
|
||||
Returns True if the parameter is missing (default: minimize to tray).
|
||||
"""
|
||||
cp = read_config_safely(CONFIG_FILE)
|
||||
if cp is None or not cp.has_section("Display") or not cp.has_option("Display", "minimize_to_tray"):
|
||||
save_minimize_to_tray(True)
|
||||
return True
|
||||
return cp.getboolean("Display", "minimize_to_tray", fallback=True)
|
||||
|
||||
def save_minimize_to_tray(minimize_to_tray):
|
||||
"""Saves the minimize-to-tray setting to the [Display] section."""
|
||||
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||
if "Display" not in cp:
|
||||
cp["Display"] = {}
|
||||
cp["Display"]["minimize_to_tray"] = str(minimize_to_tray)
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
|
||||
cp.write(configfile)
|
||||
|
||||
@@ -11,7 +11,7 @@ from PySide6.QtWidgets import QMessageBox, QDialog, QMenu, QLineEdit, QApplicati
|
||||
from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt
|
||||
from PySide6.QtGui import QDesktopServices, QIcon, QKeySequence
|
||||
from portprotonqt.localization import _
|
||||
from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites, read_favorite_folders, save_favorite_folders
|
||||
from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites, read_favorite_folders, save_favorite_folders, get_portproton_start_command
|
||||
from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam
|
||||
from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam
|
||||
from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail
|
||||
@@ -406,16 +406,7 @@ class ContextMenuManager:
|
||||
)
|
||||
return
|
||||
# Construct EGS launch command
|
||||
wrapper = "flatpak run ru.linux_gaming.PortProton"
|
||||
start_sh_path = os.path.join(self.portproton_location, "data", "scripts", "start.sh")
|
||||
if self.portproton_location and ".var" not in self.portproton_location:
|
||||
wrapper = start_sh_path
|
||||
if not os.path.exists(start_sh_path):
|
||||
self.signals.show_warning_dialog.emit(
|
||||
_("Error"),
|
||||
_("start.sh not found at {path}").format(path=start_sh_path)
|
||||
)
|
||||
return
|
||||
wrapper = get_portproton_start_command()
|
||||
exec_line = f'"{self.legendary_path}" launch {game_card.appid} --no-wine --wrapper "env START_FROM_STEAM=1 {wrapper}"'
|
||||
else:
|
||||
exec_line = self._get_exec_line(game_card.name, game_card.exec_line)
|
||||
|
||||
|
Before Width: | Height: | Size: 634 KiB After Width: | Height: | Size: 634 KiB |
|
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 315 KiB |
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 978 KiB |
|
Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 650 KiB |
|
Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 710 KiB After Width: | Height: | Size: 710 KiB |
|
Before Width: | Height: | Size: 627 KiB After Width: | Height: | Size: 627 KiB |
@@ -126,7 +126,21 @@ class FlowLayout(QLayout):
|
||||
return True
|
||||
|
||||
def heightForWidth(self, width):
|
||||
return self.doLayout(QRect(0, 0, width, 0), True)
|
||||
# Аналогично фильтруем видимые для тестового расчёта высоты
|
||||
visible_items = []
|
||||
nat_sizes = np.empty((0, 2), dtype=np.int32)
|
||||
for item in self.itemList:
|
||||
if item.widget() and item.widget().isVisible():
|
||||
visible_items.append(item)
|
||||
s = item.sizeHint()
|
||||
new_row = np.array([[s.width(), s.height()]], dtype=np.int32)
|
||||
nat_sizes = np.vstack([nat_sizes, new_row]) if len(nat_sizes) > 0 else new_row
|
||||
|
||||
if len(visible_items) == 0:
|
||||
return 0
|
||||
|
||||
_, total_height = compute_layout(nat_sizes, width, self._spacing, self._max_scale)
|
||||
return total_height
|
||||
|
||||
def setGeometry(self, rect):
|
||||
super().setGeometry(rect)
|
||||
@@ -145,26 +159,46 @@ class FlowLayout(QLayout):
|
||||
return size
|
||||
|
||||
def doLayout(self, rect, testOnly):
|
||||
N = len(self.itemList)
|
||||
if N == 0:
|
||||
N_total = len(self.itemList)
|
||||
if N_total == 0:
|
||||
return 0
|
||||
|
||||
nat_sizes = np.empty((N, 2), dtype=np.int32)
|
||||
# Фильтруем только видимые элементы
|
||||
visible_items = []
|
||||
visible_indices = [] # Индексы в оригинальном itemList для установки геометрии
|
||||
nat_sizes = np.empty((0, 2), dtype=np.int32)
|
||||
for i, item in enumerate(self.itemList):
|
||||
s = item.sizeHint()
|
||||
nat_sizes[i, 0] = s.width()
|
||||
nat_sizes[i, 1] = s.height()
|
||||
if item.widget() and item.widget().isVisible():
|
||||
visible_items.append(item)
|
||||
visible_indices.append(i)
|
||||
s = item.sizeHint()
|
||||
new_row = np.array([[s.width(), s.height()]], dtype=np.int32)
|
||||
nat_sizes = np.vstack([nat_sizes, new_row]) if len(nat_sizes) > 0 else new_row
|
||||
|
||||
N = len(visible_items)
|
||||
if N == 0:
|
||||
# Если все скрыты, устанавливаем нулевые геометрии для всех
|
||||
if not testOnly:
|
||||
for item in self.itemList:
|
||||
item.setGeometry(QRect())
|
||||
return 0
|
||||
|
||||
geom_array, total_height = compute_layout(nat_sizes, rect.width(), self._spacing, self._max_scale)
|
||||
|
||||
if not testOnly:
|
||||
for i, item in enumerate(self.itemList):
|
||||
x = geom_array[i, 0] + rect.x()
|
||||
y = geom_array[i, 1] + rect.y()
|
||||
w = geom_array[i, 2]
|
||||
h = geom_array[i, 3]
|
||||
# Устанавливаем геометрии только для видимых
|
||||
for idx, (_vis_idx, item) in enumerate(zip(visible_indices, visible_items, strict=True)):
|
||||
x = geom_array[idx, 0] + rect.x()
|
||||
y = geom_array[idx, 1] + rect.y()
|
||||
w = geom_array[idx, 2]
|
||||
h = geom_array[idx, 3]
|
||||
item.setGeometry(QRect(QPoint(x, y), QSize(w, h)))
|
||||
|
||||
# Для невидимых — нулевая геометрия
|
||||
for i in range(N_total):
|
||||
if i not in visible_indices:
|
||||
self.itemList[i].setGeometry(QRect())
|
||||
|
||||
return total_height
|
||||
|
||||
class ClickableLabel(QLabel):
|
||||
|
||||
@@ -13,7 +13,7 @@ from portprotonqt.localization import get_egs_language, _
|
||||
from portprotonqt.logger import get_logger
|
||||
from portprotonqt.image_utils import load_pixmap_async
|
||||
from portprotonqt.time_utils import parse_playtime_file, format_playtime, get_last_launch, get_last_launch_timestamp
|
||||
from portprotonqt.config_utils import get_portproton_location
|
||||
from portprotonqt.config_utils import get_portproton_location, get_portproton_start_command
|
||||
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, call_steam_api
|
||||
@@ -254,14 +254,7 @@ def add_egs_to_steam(app_name: str, game_title: str, legendary_path: str, callba
|
||||
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
|
||||
wrapper = get_portproton_start_command()
|
||||
|
||||
# Create launch script
|
||||
steam_scripts_dir = os.path.join(portproton_dir, "steam_scripts")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from PySide6.QtGui import QPainter, QColor, QDesktopServices
|
||||
from PySide6.QtCore import Signal, Property, Qt, QUrl
|
||||
from PySide6.QtCore import Signal, Property, Qt, QUrl, QTimer
|
||||
from PySide6.QtWidgets import QFrame, QGraphicsDropShadowEffect, QVBoxLayout, QWidget, QStackedLayout, QLabel
|
||||
from collections.abc import Callable
|
||||
from portprotonqt.image_utils import load_pixmap_async, round_corners
|
||||
@@ -12,6 +12,7 @@ from portprotonqt.downloader import Downloader
|
||||
from portprotonqt.animations import GameCardAnimations
|
||||
from typing import cast
|
||||
|
||||
|
||||
class GameCard(QFrame):
|
||||
borderWidthChanged = Signal()
|
||||
gradientAngleChanged = Signal()
|
||||
@@ -403,6 +404,13 @@ class GameCard(QFrame):
|
||||
self.favoriteLabel.setText("☆")
|
||||
self.favoriteLabel.setStyleSheet(self.theme.FAVORITE_LABEL_STYLE)
|
||||
|
||||
parent = self.parent()
|
||||
while parent:
|
||||
if hasattr(parent, 'game_library_manager'):
|
||||
QTimer.singleShot(0, parent.game_library_manager.update_game_grid) # type: ignore[attr-defined]
|
||||
break
|
||||
parent = parent.parent()
|
||||
|
||||
def toggle_favorite(self):
|
||||
favorites = read_favorites()
|
||||
if self.is_favorite:
|
||||
@@ -447,6 +455,7 @@ class GameCard(QFrame):
|
||||
gradientAngle = Property(float, getGradientAngle, setGradientAngle, None, "", notify=cast(Callable[[], None], gradientAngleChanged))
|
||||
scale = Property(float, getScale, setScale, None, "", notify=cast(Callable[[], None], scaleChanged))
|
||||
|
||||
|
||||
def paintEvent(self, event):
|
||||
super().paintEvent(event)
|
||||
self.animations.paint_border(QPainter(self))
|
||||
|
||||
@@ -33,8 +33,10 @@ class MainWindowProtocol(Protocol):
|
||||
# Required attributes
|
||||
searchEdit: CustomLineEdit
|
||||
_last_card_width: int
|
||||
card_width: int
|
||||
current_hovered_card: GameCard | None
|
||||
current_focused_card: GameCard | None
|
||||
gamesListWidget: QWidget | None
|
||||
|
||||
class GameLibraryManager:
|
||||
def __init__(self, main_window: MainWindowProtocol, theme, context_menu_manager: ContextMenuManager | None):
|
||||
@@ -127,6 +129,8 @@ class GameLibraryManager:
|
||||
self.card_width = self.sizeSlider.value()
|
||||
self.sizeSlider.setToolTip(f"{self.card_width} px")
|
||||
save_card_size(self.card_width)
|
||||
self.main_window.card_width = self.card_width
|
||||
self.main_window._last_card_width = self.card_width
|
||||
for card in self.game_card_cache.values():
|
||||
card.update_card_size(self.card_width)
|
||||
self.update_game_grid()
|
||||
@@ -216,6 +220,16 @@ class GameLibraryManager:
|
||||
else:
|
||||
self._update_game_grid_immediate()
|
||||
|
||||
def force_update_cards_library(self):
|
||||
if self.gamesListWidget and self.gamesListLayout:
|
||||
self.gamesListLayout.invalidate()
|
||||
self.gamesListWidget.updateGeometry()
|
||||
widget = self.gamesListWidget
|
||||
QTimer.singleShot(0, lambda: (
|
||||
widget.adjustSize(),
|
||||
widget.updateGeometry()
|
||||
))
|
||||
|
||||
def _update_game_grid_immediate(self):
|
||||
"""Updates the game grid with the provided or current game list."""
|
||||
if self.gamesListLayout is None or self.gamesListWidget is None:
|
||||
@@ -345,6 +359,8 @@ class GameLibraryManager:
|
||||
self.gamesListWidget.updateGeometry()
|
||||
self.main_window._last_card_width = self.card_width
|
||||
|
||||
self.force_update_cards_library()
|
||||
|
||||
self.is_filtering = False # Reset flag in any case
|
||||
|
||||
def _apply_filter_visibility(self, search_text: str):
|
||||
@@ -362,8 +378,9 @@ class GameLibraryManager:
|
||||
cover_path, width, height, callback = self.pending_images.pop(game_key)
|
||||
load_pixmap_async(cover_path, width, height, callback)
|
||||
|
||||
# Force geometry update so FlowLayout accounts for hidden widgets
|
||||
# Force full relayout after visibility changes
|
||||
if self.gamesListLayout is not None:
|
||||
self.gamesListLayout.invalidate() # Принудительно инвалидируем для пересчёта
|
||||
self.gamesListLayout.update()
|
||||
if self.gamesListWidget is not None:
|
||||
self.gamesListWidget.updateGeometry()
|
||||
|
||||
@@ -83,6 +83,43 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обработки URL {cover}: {e}")
|
||||
|
||||
# SteamGridDB (SGDB)
|
||||
if cover and cover.startswith("https://cdn2.steamgriddb.com"):
|
||||
try:
|
||||
parts = cover.split("/")
|
||||
filename = parts[-1] if parts else "sgdb_cover.png"
|
||||
# SGDB ссылки содержат уникальный хеш в названии — используем как имя
|
||||
local_path = os.path.join(image_folder, filename)
|
||||
|
||||
if os.path.exists(local_path):
|
||||
pixmap = QPixmap(local_path)
|
||||
finish_with(pixmap)
|
||||
return
|
||||
|
||||
def on_downloaded(result: str | None):
|
||||
pixmap = QPixmap()
|
||||
if result and os.path.exists(result):
|
||||
pixmap.load(result)
|
||||
if pixmap.isNull():
|
||||
placeholder_path = theme_manager.get_theme_image("placeholder", current_theme_name)
|
||||
if placeholder_path and QFile.exists(placeholder_path):
|
||||
pixmap.load(placeholder_path)
|
||||
else:
|
||||
pixmap = QPixmap(width, height)
|
||||
pixmap.fill(QColor("#333333"))
|
||||
painter = QPainter(pixmap)
|
||||
painter.setPen(QPen(QColor("white")))
|
||||
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, "No Image")
|
||||
painter.end()
|
||||
finish_with(pixmap)
|
||||
|
||||
logger.info("Downloading SGDB cover for %s -> %s", app_name or "unknown", filename)
|
||||
downloader.download_async(cover, local_path, timeout=5, callback=on_downloaded)
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка обработки SGDB URL {cover}: {e}")
|
||||
|
||||
if cover and cover.startswith(("http://", "https://")):
|
||||
try:
|
||||
local_path = os.path.join(image_folder, f"{app_name}.jpg")
|
||||
|
||||
73
portprotonqt/keyboard_layouts.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# keyboard_layouts.py
|
||||
keyboard_layouts = {
|
||||
'en': {
|
||||
'normal': [
|
||||
['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='],
|
||||
['TAB', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'],
|
||||
['CAPS', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'"],
|
||||
['⬆', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/']
|
||||
],
|
||||
'shift': [
|
||||
['~', '!', '@', '#', '$', '%', '^', '&&', '*', '(', ')', '_', '+'],
|
||||
['TAB', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'],
|
||||
['CAPS', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"'],
|
||||
['⬆', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?']
|
||||
]
|
||||
},
|
||||
'ru': {
|
||||
'normal': [
|
||||
['ё', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='],
|
||||
['TAB', 'й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х', 'ъ', '\\'],
|
||||
['CAPS', 'ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э'],
|
||||
['⬆', 'я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю', '.']
|
||||
],
|
||||
'shift': [
|
||||
['Ё', '!', '"', '№', ';', '%', ':', '?', '*', '(', ')', '_', '+'],
|
||||
['TAB', 'Й', 'Ц', 'У', 'К', 'Е', 'Н', 'Г', 'Ш', 'Щ', 'З', 'Х', 'Ъ', '/'],
|
||||
['CAPS', 'Ф', 'Ы', 'В', 'А', 'П', 'Р', 'О', 'Л', 'Д', 'Ж', 'Э'],
|
||||
['⬆', 'Я', 'Ч', 'С', 'М', 'И', 'Т', 'Ь', 'Б', 'Ю', ',']
|
||||
]
|
||||
},
|
||||
'fr': {
|
||||
'normal': [
|
||||
['²', '&', 'é', '"', "'", '(', '-', 'è', '_', 'ç', 'à', ')', '='],
|
||||
['TAB', 'a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '^', '$', '*'],
|
||||
['CAPS', 'q', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', 'ù'],
|
||||
['⬆', 'w', 'x', 'c', 'v', 'b', 'n', ',', ';', ':', '!']
|
||||
],
|
||||
'shift': [
|
||||
['', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '°', '+'],
|
||||
['TAB', 'A', 'Z', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '¨', '£', 'µ'],
|
||||
['CAPS', 'Q', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', '%'],
|
||||
['⬆', 'W', 'X', 'C', 'V', 'B', 'N', '?', '.', '/', '§']
|
||||
]
|
||||
},
|
||||
'es': {
|
||||
'normal': [
|
||||
['º', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', "'", '¡'],
|
||||
['TAB', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '`', '+', '\\'],
|
||||
['CAPS', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ñ', "'", 'ç'],
|
||||
['⬆', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '-']
|
||||
],
|
||||
'shift': [
|
||||
['ª', '!', '"', '·', '$', '%', '&', '/', '(', ')', '=', '?', '¿'],
|
||||
['TAB', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '^', '*', '|'],
|
||||
['CAPS', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Ñ', '"', 'Ç'],
|
||||
['⬆', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ';', ':', '_']
|
||||
]
|
||||
},
|
||||
'de': {
|
||||
'normal': [
|
||||
['^', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'ß', '´'],
|
||||
['TAB', 'q', 'w', 'e', 'r', 't', 'z', 'u', 'i', 'o', 'p', 'ü', '+', '#'],
|
||||
['CAPS', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ö', 'ä'],
|
||||
['⬆', '<', 'y', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '-']
|
||||
],
|
||||
'shift': [
|
||||
['°', '!', '"', '§', '$', '%', '&', '/', '(', ')', '=', '?', '`'],
|
||||
['TAB', 'Q', 'W', 'E', 'R', 'T', 'Z', 'U', 'I', 'O', 'P', 'Ü', '*', '\''],
|
||||
['CAPS', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Ö', 'Ä'],
|
||||
['⬆', '>', 'Y', 'X', 'C', 'V', 'B', 'N', 'M', ';', ':', '_']
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-09-23 22:23+0500\n"
|
||||
"POT-Creation-Date: 2025-10-16 14:54+0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: de_DE\n"
|
||||
@@ -191,6 +191,10 @@ msgstr ""
|
||||
msgid "Failed to delete custom data: {error}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Added '{game_name}' successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "Game name and executable path are required"
|
||||
msgstr ""
|
||||
|
||||
@@ -248,13 +252,37 @@ msgstr ""
|
||||
msgid "Select All"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Dir"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prev Dir"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Force Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prev Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Next Tab"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "File Explorer"
|
||||
msgstr ""
|
||||
|
||||
@@ -304,6 +332,39 @@ msgstr ""
|
||||
msgid "No cover selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix Manager"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set"
|
||||
msgstr ""
|
||||
|
||||
msgid "Libraries"
|
||||
msgstr ""
|
||||
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fonts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Winetricks not found. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid "No components selected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation failed. Check logs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Components installed successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading Epic Games Store games..."
|
||||
msgstr ""
|
||||
|
||||
@@ -352,9 +413,6 @@ msgstr ""
|
||||
msgid "Auto Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Emulators"
|
||||
msgstr ""
|
||||
|
||||
msgid "Wine Settings"
|
||||
msgstr ""
|
||||
|
||||
@@ -370,6 +428,28 @@ msgstr ""
|
||||
msgid "Fullscreen"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation already in progress."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start installation."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Processed {} installation..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation completed successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading Steam games..."
|
||||
msgstr ""
|
||||
|
||||
@@ -382,13 +462,106 @@ msgstr ""
|
||||
msgid "Find Games ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Here you can configure automatic game installation..."
|
||||
#, python-brace-format
|
||||
msgid "Added '{name}'"
|
||||
msgstr ""
|
||||
|
||||
msgid "List of available emulators and their configuration..."
|
||||
msgid "Compatibility tool:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Various Wine parameters and versions..."
|
||||
msgid "Prefix:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Wine Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Registry Editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Command Prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uninstaller"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create Prefix Backup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load Prefix Backup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete Compatibility Tool"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete Prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear Prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Launching tool..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm Clear"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to clear prefix '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' cleared successfully."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Prefix '{}' cleared with errors:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start backup process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start restore process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix backup completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix backup failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix restore completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix restore failed."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete prefix '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete prefix: {}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete compatibility tool '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Compatibility tool '{}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete compatibility tool: {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main PortProton parameters..."
|
||||
@@ -424,6 +597,9 @@ msgstr ""
|
||||
msgid "Games Display Filter:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Gamepad Type:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -448,6 +624,12 @@ msgstr ""
|
||||
msgid "Application Fullscreen Mode:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimize to tray on close"
|
||||
msgstr ""
|
||||
|
||||
msgid "Application Close Mode:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto Fullscreen on Gamepad connected"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-09-23 22:23+0500\n"
|
||||
"POT-Creation-Date: 2025-10-16 14:54+0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language: es_ES\n"
|
||||
@@ -191,6 +191,10 @@ msgstr ""
|
||||
msgid "Failed to delete custom data: {error}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Added '{game_name}' successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "Game name and executable path are required"
|
||||
msgstr ""
|
||||
|
||||
@@ -248,13 +252,37 @@ msgstr ""
|
||||
msgid "Select All"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Dir"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prev Dir"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Force Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prev Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Next Tab"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "File Explorer"
|
||||
msgstr ""
|
||||
|
||||
@@ -304,6 +332,39 @@ msgstr ""
|
||||
msgid "No cover selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix Manager"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set"
|
||||
msgstr ""
|
||||
|
||||
msgid "Libraries"
|
||||
msgstr ""
|
||||
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fonts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Winetricks not found. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid "No components selected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation failed. Check logs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Components installed successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading Epic Games Store games..."
|
||||
msgstr ""
|
||||
|
||||
@@ -352,9 +413,6 @@ msgstr ""
|
||||
msgid "Auto Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Emulators"
|
||||
msgstr ""
|
||||
|
||||
msgid "Wine Settings"
|
||||
msgstr ""
|
||||
|
||||
@@ -370,6 +428,28 @@ msgstr ""
|
||||
msgid "Fullscreen"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation already in progress."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start installation."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Processed {} installation..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation completed successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading Steam games..."
|
||||
msgstr ""
|
||||
|
||||
@@ -382,13 +462,106 @@ msgstr ""
|
||||
msgid "Find Games ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Here you can configure automatic game installation..."
|
||||
#, python-brace-format
|
||||
msgid "Added '{name}'"
|
||||
msgstr ""
|
||||
|
||||
msgid "List of available emulators and their configuration..."
|
||||
msgid "Compatibility tool:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Various Wine parameters and versions..."
|
||||
msgid "Prefix:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Wine Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Registry Editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Command Prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uninstaller"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create Prefix Backup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load Prefix Backup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete Compatibility Tool"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete Prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear Prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Launching tool..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm Clear"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to clear prefix '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' cleared successfully."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Prefix '{}' cleared with errors:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start backup process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start restore process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix backup completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix backup failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix restore completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix restore failed."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete prefix '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete prefix: {}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete compatibility tool '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Compatibility tool '{}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete compatibility tool: {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main PortProton parameters..."
|
||||
@@ -424,6 +597,9 @@ msgstr ""
|
||||
msgid "Games Display Filter:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Gamepad Type:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -448,6 +624,12 @@ msgstr ""
|
||||
msgid "Application Fullscreen Mode:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimize to tray on close"
|
||||
msgstr ""
|
||||
|
||||
msgid "Application Close Mode:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto Fullscreen on Gamepad connected"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PortProtonQt 0.1.1\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-09-23 22:23+0500\n"
|
||||
"POT-Creation-Date: 2025-10-16 14:54+0500\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -189,6 +189,10 @@ msgstr ""
|
||||
msgid "Failed to delete custom data: {error}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Added '{game_name}' successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "Game name and executable path are required"
|
||||
msgstr ""
|
||||
|
||||
@@ -246,13 +250,37 @@ msgstr ""
|
||||
msgid "Select All"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
msgid "Select Dir"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prev Dir"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr ""
|
||||
|
||||
msgid "Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Force Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prev Tab"
|
||||
msgstr ""
|
||||
|
||||
msgid "Next Tab"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgstr ""
|
||||
|
||||
msgid "File Explorer"
|
||||
msgstr ""
|
||||
|
||||
@@ -302,6 +330,39 @@ msgstr ""
|
||||
msgid "No cover selected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix Manager"
|
||||
msgstr ""
|
||||
|
||||
msgid "Set"
|
||||
msgstr ""
|
||||
|
||||
msgid "Libraries"
|
||||
msgstr ""
|
||||
|
||||
msgid "Information"
|
||||
msgstr ""
|
||||
|
||||
msgid "Fonts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Winetricks not found. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Warning"
|
||||
msgstr ""
|
||||
|
||||
msgid "No components selected."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation failed. Check logs."
|
||||
msgstr ""
|
||||
|
||||
msgid "Components installed successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading Epic Games Store games..."
|
||||
msgstr ""
|
||||
|
||||
@@ -350,9 +411,6 @@ msgstr ""
|
||||
msgid "Auto Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "Emulators"
|
||||
msgstr ""
|
||||
|
||||
msgid "Wine Settings"
|
||||
msgstr ""
|
||||
|
||||
@@ -368,6 +426,28 @@ msgstr ""
|
||||
msgid "Fullscreen"
|
||||
msgstr ""
|
||||
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation already in progress."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start installation."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Processed {} installation..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation completed successfully."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Installation error."
|
||||
msgstr ""
|
||||
|
||||
msgid "Loading Steam games..."
|
||||
msgstr ""
|
||||
|
||||
@@ -380,13 +460,106 @@ msgstr ""
|
||||
msgid "Find Games ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Here you can configure automatic game installation..."
|
||||
#, python-brace-format
|
||||
msgid "Added '{name}'"
|
||||
msgstr ""
|
||||
|
||||
msgid "List of available emulators and their configuration..."
|
||||
msgid "Compatibility tool:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Various Wine parameters and versions..."
|
||||
msgid "Prefix:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Wine Configuration"
|
||||
msgstr ""
|
||||
|
||||
msgid "Registry Editor"
|
||||
msgstr ""
|
||||
|
||||
msgid "Command Prompt"
|
||||
msgstr ""
|
||||
|
||||
msgid "Uninstaller"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create Prefix Backup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Load Prefix Backup"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete Compatibility Tool"
|
||||
msgstr ""
|
||||
|
||||
msgid "Delete Prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Clear Prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Launching tool..."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Confirm Clear"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to clear prefix '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' cleared successfully."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Prefix '{}' cleared with errors:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start backup process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Failed to start restore process."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix backup completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix backup failed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix restore completed."
|
||||
msgstr ""
|
||||
|
||||
msgid "Prefix restore failed."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete prefix '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete prefix: {}"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete compatibility tool '{}'?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Compatibility tool '{}' deleted."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete compatibility tool: {}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Main PortProton parameters..."
|
||||
@@ -422,6 +595,9 @@ msgstr ""
|
||||
msgid "Games Display Filter:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Gamepad Type:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Proxy URL"
|
||||
msgstr ""
|
||||
|
||||
@@ -446,6 +622,12 @@ msgstr ""
|
||||
msgid "Application Fullscreen Mode:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Minimize to tray on close"
|
||||
msgstr ""
|
||||
|
||||
msgid "Application Close Mode:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Auto Fullscreen on Gamepad connected"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -9,18 +9,17 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2025-09-23 22:23+0500\n"
|
||||
"PO-Revision-Date: 2025-09-23 22:23+0500\n"
|
||||
"POT-Creation-Date: 2025-10-16 14:54+0500\n"
|
||||
"PO-Revision-Date: 2025-10-16 14:54+0500\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: ru_RU <LL@li.org>\n"
|
||||
"Language: ru_RU\n"
|
||||
"Language-Team: ru_RU <LL@li.org>\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
||||
"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 "
|
||||
"&& (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
"Generated-By: Babel 2.17.0\n"
|
||||
"X-Generator: Poedit 3.6\n"
|
||||
|
||||
msgid "Error"
|
||||
msgstr "Ошибка"
|
||||
@@ -87,11 +86,11 @@ msgstr "Успешно"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"'{game_name}' was added to Steam. Please restart Steam for changes to take "
|
||||
"effect."
|
||||
"'{game_name}' was added to Steam. Please restart Steam for changes to "
|
||||
"take effect."
|
||||
msgstr ""
|
||||
"'{game_name}' был(а) добавлен(а) в Steam. Пожалуйста, перезапустите Steam, "
|
||||
"чтобы изменения вступили в силу."
|
||||
"'{game_name}' был(а) добавлен(а) в Steam. Пожалуйста, перезапустите "
|
||||
"Steam, чтобы изменения вступили в силу."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Executable not found for game: {game_name}"
|
||||
@@ -179,11 +178,11 @@ msgstr "Подтвердите удаление"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete '{game_name}'? This will remove the .desktop "
|
||||
"file and custom data."
|
||||
"Are you sure you want to delete '{game_name}'? This will remove the "
|
||||
".desktop file and custom data."
|
||||
msgstr ""
|
||||
"Вы уверены, что хотите удалить '{game_name}'? Это приведёт к удалению файла ."
|
||||
"desktop и пользовательских данных."
|
||||
"Вы уверены, что хотите удалить '{game_name}'? Это приведёт к удалению "
|
||||
"файла .desktop и пользовательских данных."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete .desktop file: {error}"
|
||||
@@ -197,6 +196,10 @@ msgstr "'{game_name}' был(а) успешно удалён(а)"
|
||||
msgid "Failed to delete custom data: {error}"
|
||||
msgstr "Не удалось удалить пользовательские данные: {error}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Added '{game_name}' successfully"
|
||||
msgstr "'{game_name}' успешно добавлен(а)"
|
||||
|
||||
msgid "Game name and executable path are required"
|
||||
msgstr "Требуются название игры и путь к исполняемому файлу"
|
||||
|
||||
@@ -225,11 +228,11 @@ msgstr "Не удалось добавить '{game_name}' в Steam: {error}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"'{game_name}' was removed from Steam. Please restart Steam for changes to take "
|
||||
"effect."
|
||||
"'{game_name}' was removed from Steam. Please restart Steam for changes to"
|
||||
" take effect."
|
||||
msgstr ""
|
||||
"'{game_name}' был(а) удалён(а) из Steam. Пожалуйста, перезапустите Steam, чтобы "
|
||||
"изменения вступили в силу."
|
||||
"'{game_name}' был(а) удалён(а) из Steam. Пожалуйста, перезапустите Steam,"
|
||||
" чтобы изменения вступили в силу."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to remove game '{game_name}' from Steam: {error}"
|
||||
@@ -256,13 +259,37 @@ msgstr "Удалить"
|
||||
msgid "Select All"
|
||||
msgstr "Выбрать всё"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgstr "Идёт запуск {0}"
|
||||
msgid "Open"
|
||||
msgstr "Открыть"
|
||||
|
||||
msgid "Select Dir"
|
||||
msgstr "Выбрать папку"
|
||||
|
||||
msgid "Prev Dir"
|
||||
msgstr "Предыдущий каталог"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Отмена"
|
||||
|
||||
msgid "Toggle"
|
||||
msgstr "Переключить"
|
||||
|
||||
msgid "Install"
|
||||
msgstr "Установить"
|
||||
|
||||
msgid "Force Install"
|
||||
msgstr "Принудительно установить"
|
||||
|
||||
msgid "Prev Tab"
|
||||
msgstr "Предыдущая вкладка"
|
||||
|
||||
msgid "Next Tab"
|
||||
msgstr "Следующая вкладка"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Launching {0}"
|
||||
msgstr "Идёт запуск {0}"
|
||||
|
||||
msgid "File Explorer"
|
||||
msgstr "Проводник"
|
||||
|
||||
@@ -274,7 +301,7 @@ msgstr "Путь: "
|
||||
|
||||
#, python-format
|
||||
msgid "Access denied: %s"
|
||||
msgstr "Доступ запрещен: %s"
|
||||
msgstr "Доступ запрещён: %s"
|
||||
|
||||
msgid "Edit Game"
|
||||
msgstr "Редактировать игру"
|
||||
@@ -312,6 +339,39 @@ msgstr "Скачивание обложки..."
|
||||
msgid "No cover selected"
|
||||
msgstr "Обложка не выбрана"
|
||||
|
||||
msgid "Prefix Manager"
|
||||
msgstr "Менеджер префиксов"
|
||||
|
||||
msgid "Set"
|
||||
msgstr "Выбор"
|
||||
|
||||
msgid "Libraries"
|
||||
msgstr "Библиотеки"
|
||||
|
||||
msgid "Information"
|
||||
msgstr "Описание"
|
||||
|
||||
msgid "Fonts"
|
||||
msgstr "Шрифты"
|
||||
|
||||
msgid "Settings"
|
||||
msgstr "Настройки"
|
||||
|
||||
msgid "Winetricks not found. Please try again."
|
||||
msgstr "Winetricks не найден. Повторите попытку."
|
||||
|
||||
msgid "Warning"
|
||||
msgstr "Предупреждение"
|
||||
|
||||
msgid "No components selected."
|
||||
msgstr "Не выбрано ни одного компонента."
|
||||
|
||||
msgid "Installation failed. Check logs."
|
||||
msgstr "Установка не удалась. Проверьте журналы."
|
||||
|
||||
msgid "Components installed successfully."
|
||||
msgstr "Компоненты успешно установлены."
|
||||
|
||||
msgid "Loading Epic Games Store games..."
|
||||
msgstr "Загрузка игр из Epic Games Store..."
|
||||
|
||||
@@ -360,9 +420,6 @@ msgstr "Библиотека"
|
||||
msgid "Auto Install"
|
||||
msgstr "Автоустановка"
|
||||
|
||||
msgid "Emulators"
|
||||
msgstr "Эмуляторы"
|
||||
|
||||
msgid "Wine Settings"
|
||||
msgstr "Настройки wine"
|
||||
|
||||
@@ -378,6 +435,28 @@ msgstr "Назад"
|
||||
msgid "Fullscreen"
|
||||
msgstr "Полный экран"
|
||||
|
||||
msgid "Search"
|
||||
msgstr "Поиск"
|
||||
|
||||
msgid "Installation already in progress."
|
||||
msgstr "Установка уже выполняется."
|
||||
|
||||
msgid "Failed to start installation."
|
||||
msgstr "Не удалось запустить установку."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Processed {} installation..."
|
||||
msgstr "В процессе установки {}..."
|
||||
|
||||
msgid "Installation completed successfully."
|
||||
msgstr "Установка завершена успешно."
|
||||
|
||||
msgid "Installation failed."
|
||||
msgstr "Установка не удалась."
|
||||
|
||||
msgid "Installation error."
|
||||
msgstr "Ошибка установки."
|
||||
|
||||
msgid "Loading Steam games..."
|
||||
msgstr "Загрузка игр из Steam..."
|
||||
|
||||
@@ -390,14 +469,109 @@ msgstr "Игровая библиотека"
|
||||
msgid "Find Games ..."
|
||||
msgstr "Найти игры..."
|
||||
|
||||
msgid "Here you can configure automatic game installation..."
|
||||
msgstr "Здесь можно настроить автоматическую установку игр..."
|
||||
#, python-brace-format
|
||||
msgid "Added '{name}'"
|
||||
msgstr "'{name}' добавлен(а)"
|
||||
|
||||
msgid "List of available emulators and their configuration..."
|
||||
msgstr "Список доступных эмуляторов и их настройка..."
|
||||
msgid "Compatibility tool:"
|
||||
msgstr "Инструмент совместимости:"
|
||||
|
||||
msgid "Various Wine parameters and versions..."
|
||||
msgstr "Различные параметры и версии wine..."
|
||||
msgid "Prefix:"
|
||||
msgstr "Префикс:"
|
||||
|
||||
msgid "Wine Configuration"
|
||||
msgstr "Конфигурация Wine"
|
||||
|
||||
msgid "Registry Editor"
|
||||
msgstr "Редактор реестра"
|
||||
|
||||
msgid "Command Prompt"
|
||||
msgstr "Командная строка"
|
||||
|
||||
msgid "Uninstaller"
|
||||
msgstr "Удаление программ"
|
||||
|
||||
msgid "Create Prefix Backup"
|
||||
msgstr "Создать резервную копию префикса"
|
||||
|
||||
msgid "Load Prefix Backup"
|
||||
msgstr "Загрузить резервную копию префикса"
|
||||
|
||||
msgid "Delete Compatibility Tool"
|
||||
msgstr "Удалить Инструмент совместимости"
|
||||
|
||||
msgid "Delete Prefix"
|
||||
msgstr "Удалить Префикс"
|
||||
|
||||
msgid "Clear Prefix"
|
||||
msgstr "Очистить Префикс"
|
||||
|
||||
msgid "Launching tool..."
|
||||
msgstr "Запуск инструмента..."
|
||||
|
||||
msgid "Failed to start process."
|
||||
msgstr "Не удалось запустить процесс."
|
||||
|
||||
msgid "Confirm Clear"
|
||||
msgstr "Подтвердите очистку"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to clear prefix '{}'?"
|
||||
msgstr "Вы уверены, что хотите очистить префикс «{}»?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' cleared successfully."
|
||||
msgstr "Префикс '{}' успешно удален."
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"Prefix '{}' cleared with errors:\n"
|
||||
"{}"
|
||||
msgstr ""
|
||||
"Префикс '{}' очищен с ошибками:\n"
|
||||
"{}"
|
||||
|
||||
msgid "Failed to start backup process."
|
||||
msgstr "Не удалось запустить процесс резервного копирования."
|
||||
|
||||
msgid "Failed to start restore process."
|
||||
msgstr "Не удалось запустить процесс восстановления."
|
||||
|
||||
msgid "Prefix backup completed."
|
||||
msgstr "Резервное копирование префикса завершено."
|
||||
|
||||
msgid "Prefix backup failed."
|
||||
msgstr "Сбой резервного копирования префикса."
|
||||
|
||||
msgid "Prefix restore completed."
|
||||
msgstr "Восстановление префикса завершено."
|
||||
|
||||
msgid "Prefix restore failed."
|
||||
msgstr "Восстановление префикса не удалось."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete prefix '{}'?"
|
||||
msgstr "Вы уверены, что хотите удалить префикс «{}»?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Prefix '{}' deleted."
|
||||
msgstr "Префикс «{}» удален."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete prefix: {}"
|
||||
msgstr "Не удалось удалить префикс: {}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Are you sure you want to delete compatibility tool '{}'?"
|
||||
msgstr "Вы уверены, что хотите удалить инструмент совместимости «{}»?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Compatibility tool '{}' deleted."
|
||||
msgstr "Инструмент совместимости «{}» удален."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Failed to delete compatibility tool: {}"
|
||||
msgstr "Не удалось удалить инструмент совместимости: {}"
|
||||
|
||||
msgid "Main PortProton parameters..."
|
||||
msgstr "Основные параметры PortProton..."
|
||||
@@ -432,6 +606,9 @@ msgstr "все"
|
||||
msgid "Games Display Filter:"
|
||||
msgstr "Фильтр игр:"
|
||||
|
||||
msgid "Gamepad Type:"
|
||||
msgstr "Тип геймпада:"
|
||||
|
||||
msgid "Proxy URL"
|
||||
msgstr "Адрес прокси"
|
||||
|
||||
@@ -456,6 +633,12 @@ msgstr "Запуск приложения в полноэкранном режи
|
||||
msgid "Application Fullscreen Mode:"
|
||||
msgstr "Режим полноэкранного отображения приложения:"
|
||||
|
||||
msgid "Minimize to tray on close"
|
||||
msgstr "Сворачивать в трей при закрытии"
|
||||
|
||||
msgid "Application Close Mode:"
|
||||
msgstr "Режим закрытия приложения:"
|
||||
|
||||
msgid "Auto Fullscreen on Gamepad connected"
|
||||
msgstr "Режим полноэкранного отображения приложения при подключении геймпада"
|
||||
|
||||
@@ -482,7 +665,8 @@ msgstr "Подтвердите удаление"
|
||||
|
||||
msgid "Are you sure you want to reset all settings? This action cannot be undone."
|
||||
msgstr ""
|
||||
"Вы уверены, что хотите сбросить все настройки? Это действие нельзя отменить."
|
||||
"Вы уверены, что хотите сбросить все настройки? Это действие нельзя "
|
||||
"отменить."
|
||||
|
||||
msgid "Settings reset. Restarting..."
|
||||
msgstr "Настройки сброшены. Перезапуск..."
|
||||
@@ -654,3 +838,4 @@ msgstr "Нет избранных"
|
||||
|
||||
msgid "No recent games"
|
||||
msgstr "Нет недавних игр"
|
||||
|
||||
|
||||
@@ -4,12 +4,18 @@ import orjson
|
||||
import requests
|
||||
import urllib.parse
|
||||
import time
|
||||
import glob
|
||||
import re
|
||||
import hashlib
|
||||
from collections.abc import Callable
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from portprotonqt.downloader import Downloader
|
||||
from portprotonqt.logger import get_logger
|
||||
from portprotonqt.config_utils import get_portproton_location
|
||||
|
||||
logger = get_logger(__name__)
|
||||
CACHE_DURATION = 30 * 24 * 60 * 60 # 30 days in seconds
|
||||
AUTOINSTALL_CACHE_DURATION = 3600 # 1 hour for autoinstall cache
|
||||
|
||||
def normalize_name(s):
|
||||
"""
|
||||
@@ -52,7 +58,11 @@ class PortProtonAPI:
|
||||
self.xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
|
||||
self.custom_data_dir = os.path.join(self.xdg_data_home, "PortProtonQt", "custom_data")
|
||||
os.makedirs(self.custom_data_dir, exist_ok=True)
|
||||
self.portproton_location = get_portproton_location()
|
||||
self.repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
self.builtin_custom_folder = os.path.join(self.repo_root, "custom_data")
|
||||
self._topics_data = None
|
||||
self._autoinstall_cache = None # New: In-memory cache
|
||||
|
||||
def _get_game_dir(self, exe_name: str) -> str:
|
||||
game_dir = os.path.join(self.custom_data_dir, exe_name)
|
||||
@@ -68,40 +78,6 @@ class PortProtonAPI:
|
||||
logger.debug(f"Failed to check file at {url}: {e}")
|
||||
return False
|
||||
|
||||
def download_game_assets(self, exe_name: str, timeout: int = 5) -> dict[str, str | None]:
|
||||
game_dir = self._get_game_dir(exe_name)
|
||||
results: dict[str, str | None] = {"cover": None, "metadata": None}
|
||||
cover_extensions = [".png", ".jpg", ".jpeg", ".bmp"]
|
||||
cover_url_base = f"{self.base_url}/{exe_name}/cover"
|
||||
metadata_url = f"{self.base_url}/{exe_name}/metadata.txt"
|
||||
|
||||
for ext in cover_extensions:
|
||||
cover_url = f"{cover_url_base}{ext}"
|
||||
if self._check_file_exists(cover_url, timeout):
|
||||
local_cover_path = os.path.join(game_dir, f"cover{ext}")
|
||||
result = self.downloader.download(cover_url, local_cover_path, timeout=timeout)
|
||||
if result:
|
||||
results["cover"] = result
|
||||
logger.info(f"Downloaded cover for {exe_name} to {result}")
|
||||
break
|
||||
else:
|
||||
logger.error(f"Failed to download cover for {exe_name} from {cover_url}")
|
||||
else:
|
||||
logger.debug(f"No cover found for {exe_name} with extension {ext}")
|
||||
|
||||
if self._check_file_exists(metadata_url, timeout):
|
||||
local_metadata_path = os.path.join(game_dir, "metadata.txt")
|
||||
result = self.downloader.download(metadata_url, local_metadata_path, timeout=timeout)
|
||||
if result:
|
||||
results["metadata"] = result
|
||||
logger.info(f"Downloaded metadata for {exe_name} to {result}")
|
||||
else:
|
||||
logger.error(f"Failed to download metadata for {exe_name} from {metadata_url}")
|
||||
else:
|
||||
logger.debug(f"No metadata found for {exe_name}")
|
||||
|
||||
return results
|
||||
|
||||
def download_game_assets_async(self, exe_name: str, timeout: int = 5, callback: Callable[[dict[str, str | None]], None] | None = None) -> None:
|
||||
game_dir = self._get_game_dir(exe_name)
|
||||
cover_extensions = [".png", ".jpg", ".jpeg", ".bmp"]
|
||||
@@ -163,6 +139,236 @@ class PortProtonAPI:
|
||||
if callback:
|
||||
callback(results)
|
||||
|
||||
def download_autoinstall_cover_async(self, exe_name: str, timeout: int = 5, callback: Callable[[str | None], None] | None = None) -> None:
|
||||
"""Download only autoinstall cover image (PNG only, no metadata)."""
|
||||
xdg_data_home = os.getenv("XDG_DATA_HOME",
|
||||
os.path.join(os.path.expanduser("~"), ".local", "share"))
|
||||
autoinstall_root = os.path.join(xdg_data_home, "PortProtonQt", "custom_data", "autoinstall")
|
||||
user_game_folder = os.path.join(autoinstall_root, exe_name)
|
||||
|
||||
if not os.path.isdir(user_game_folder):
|
||||
try:
|
||||
os.mkdir(user_game_folder)
|
||||
except FileExistsError:
|
||||
pass
|
||||
|
||||
cover_url = f"{self.base_url}/{exe_name}/cover.png"
|
||||
local_cover_path = os.path.join(user_game_folder, "cover.png")
|
||||
|
||||
def on_cover_downloaded(local_path: str | None):
|
||||
if local_path:
|
||||
logger.info(f"Async autoinstall cover downloaded for {exe_name}: {local_path}")
|
||||
else:
|
||||
logger.debug(f"No autoinstall cover downloaded for {exe_name}")
|
||||
if callback:
|
||||
callback(local_path)
|
||||
|
||||
if self._check_file_exists(cover_url, timeout):
|
||||
self.downloader.download_async(
|
||||
cover_url,
|
||||
local_cover_path,
|
||||
timeout=timeout,
|
||||
callback=on_cover_downloaded
|
||||
)
|
||||
else:
|
||||
logger.debug(f"No autoinstall cover found for {exe_name}")
|
||||
if callback:
|
||||
callback(None)
|
||||
|
||||
def parse_autoinstall_script(self, file_path: str) -> tuple[str | None, str | None]:
|
||||
"""Extract display_name from # name comment and exe_name from autoinstall bash script."""
|
||||
try:
|
||||
with open(file_path, encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Skip emulators
|
||||
if re.search(r'#\s*type\s*:\s*emulators', content, re.IGNORECASE):
|
||||
return None, None
|
||||
|
||||
display_name = None
|
||||
exe_name = None
|
||||
|
||||
# Extract display_name from "# name:" comment
|
||||
name_match = re.search(r'#\s*name\s*:\s*(.+)', content, re.IGNORECASE)
|
||||
if name_match:
|
||||
display_name = name_match.group(1).strip()
|
||||
|
||||
# --- pw_create_unique_exe ---
|
||||
pw_match = re.search(r'pw_create_unique_exe(?:\s+["\']([^"\']+)["\'])?', content)
|
||||
if pw_match:
|
||||
arg = pw_match.group(1)
|
||||
if arg:
|
||||
exe_name = arg.strip()
|
||||
if not exe_name.lower().endswith(".exe"):
|
||||
exe_name += ".exe"
|
||||
else:
|
||||
export_match = re.search(
|
||||
r'export\s+PORTWINE_CREATE_SHORTCUT_NAME\s*=\s*["\']([^"\']+)["\']',
|
||||
content, re.IGNORECASE)
|
||||
if export_match:
|
||||
exe_name = f"{export_match.group(1).strip()}.exe"
|
||||
|
||||
else:
|
||||
portwine_match = None
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("#"):
|
||||
continue
|
||||
if "portwine_exe" in stripped and "=" in stripped:
|
||||
portwine_match = stripped
|
||||
break
|
||||
|
||||
if portwine_match:
|
||||
exe_expr = portwine_match.split("=", 1)[1].strip().strip("'\" ")
|
||||
exe_candidates = re.findall(r'[-\w\s/\\\.]+\.exe', exe_expr)
|
||||
if exe_candidates:
|
||||
exe_name = os.path.basename(exe_candidates[-1].strip())
|
||||
|
||||
|
||||
# Fallback
|
||||
if not display_name and exe_name:
|
||||
display_name = exe_name
|
||||
|
||||
return display_name, exe_name
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to parse {file_path}: {e}")
|
||||
return None, None
|
||||
|
||||
def _compute_scripts_signature(self, auto_dir: str) -> str:
|
||||
"""Compute a hash-based signature of the autoinstall scripts to detect changes."""
|
||||
if not os.path.exists(auto_dir):
|
||||
return ""
|
||||
scripts = sorted(glob.glob(os.path.join(auto_dir, "*")))
|
||||
# Simple hash: concatenate sorted filenames and hash
|
||||
filenames_str = "".join(sorted([os.path.basename(s) for s in scripts]))
|
||||
return hashlib.md5(filenames_str.encode()).hexdigest()
|
||||
|
||||
def _load_autoinstall_cache(self):
|
||||
"""Load cached autoinstall games if fresh and scripts unchanged."""
|
||||
if self._autoinstall_cache is not None:
|
||||
return self._autoinstall_cache
|
||||
cache_dir = get_cache_dir()
|
||||
cache_file = os.path.join(cache_dir, "autoinstall_games_cache.json")
|
||||
if os.path.exists(cache_file):
|
||||
try:
|
||||
mod_time = os.path.getmtime(cache_file)
|
||||
if time.time() - mod_time < AUTOINSTALL_CACHE_DURATION:
|
||||
with open(cache_file, "rb") as f:
|
||||
data = orjson.loads(f.read())
|
||||
# Check signature
|
||||
cached_signature = data.get("scripts_signature", "")
|
||||
current_signature = self._compute_scripts_signature(
|
||||
os.path.join(self.portproton_location or "", "data", "scripts", "pw_autoinstall")
|
||||
)
|
||||
if cached_signature != current_signature:
|
||||
logger.info("Scripts signature mismatch; invalidating cache")
|
||||
return None
|
||||
self._autoinstall_cache = data["games"]
|
||||
logger.info(f"Loaded {len(self._autoinstall_cache)} cached autoinstall games")
|
||||
return self._autoinstall_cache
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load autoinstall cache: {e}")
|
||||
return None
|
||||
|
||||
def _save_autoinstall_cache(self, games):
|
||||
"""Save parsed autoinstall games to cache with scripts signature."""
|
||||
try:
|
||||
cache_dir = get_cache_dir()
|
||||
cache_file = os.path.join(cache_dir, "autoinstall_games_cache.json")
|
||||
auto_dir = os.path.join(self.portproton_location or "", "data", "scripts", "pw_autoinstall")
|
||||
scripts_signature = self._compute_scripts_signature(auto_dir)
|
||||
data = {"games": games, "scripts_signature": scripts_signature, "timestamp": time.time()}
|
||||
with open(cache_file, "wb") as f:
|
||||
f.write(orjson.dumps(data))
|
||||
logger.debug(f"Saved {len(games)} autoinstall games to cache with signature {scripts_signature}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save autoinstall cache: {e}")
|
||||
|
||||
def start_autoinstall_games_load(self, callback: Callable[[list[tuple]], None]) -> QThread | None:
|
||||
"""Start loading auto-install games in a background thread. Returns the thread for management."""
|
||||
# Check cache first (sync, fast)
|
||||
cached_games = self._load_autoinstall_cache()
|
||||
if cached_games is not None:
|
||||
# Emit via callback immediately if cached
|
||||
QThread.msleep(0) # Yield to Qt event loop
|
||||
callback(cached_games)
|
||||
return None # No thread needed
|
||||
|
||||
# No cache: Start background thread
|
||||
class AutoinstallWorker(QThread):
|
||||
finished = Signal(list)
|
||||
api: "PortProtonAPI"
|
||||
portproton_location: str | None
|
||||
|
||||
def run(self):
|
||||
games = []
|
||||
auto_dir = os.path.join(
|
||||
self.portproton_location or "", "data", "scripts", "pw_autoinstall"
|
||||
) if self.portproton_location else ""
|
||||
if not os.path.exists(auto_dir):
|
||||
self.finished.emit(games)
|
||||
return
|
||||
|
||||
scripts = sorted(glob.glob(os.path.join(auto_dir, "*")))
|
||||
if not scripts:
|
||||
self.finished.emit(games)
|
||||
return
|
||||
|
||||
xdg_data_home = os.getenv(
|
||||
"XDG_DATA_HOME",
|
||||
os.path.join(os.path.expanduser("~"), ".local", "share"),
|
||||
)
|
||||
base_autoinstall_dir = os.path.join(
|
||||
xdg_data_home, "PortProtonQt", "custom_data", "autoinstall"
|
||||
)
|
||||
os.makedirs(base_autoinstall_dir, exist_ok=True)
|
||||
|
||||
for script_path in scripts:
|
||||
display_name, exe_name = self.api.parse_autoinstall_script(script_path)
|
||||
script_name = os.path.splitext(os.path.basename(script_path))[0]
|
||||
|
||||
if not (display_name and exe_name):
|
||||
continue
|
||||
|
||||
exe_name = os.path.splitext(exe_name)[0]
|
||||
user_game_folder = os.path.join(base_autoinstall_dir, exe_name)
|
||||
os.makedirs(user_game_folder, exist_ok=True)
|
||||
|
||||
# Find cover
|
||||
cover_path = ""
|
||||
user_files = (
|
||||
set(os.listdir(user_game_folder))
|
||||
if os.path.exists(user_game_folder)
|
||||
else set()
|
||||
)
|
||||
for ext in [".jpg", ".png", ".jpeg", ".bmp"]:
|
||||
candidate = f"cover{ext}"
|
||||
if candidate in user_files:
|
||||
cover_path = os.path.join(user_game_folder, candidate)
|
||||
break
|
||||
|
||||
if not cover_path:
|
||||
logger.debug(f"No local cover found for autoinstall {exe_name}")
|
||||
|
||||
game_tuple = (
|
||||
display_name, "", cover_path, "", f"autoinstall:{script_name}",
|
||||
"", "Never", "0h 0m", "", "", 0, 0, "autoinstall", exe_name
|
||||
)
|
||||
games.append(game_tuple)
|
||||
|
||||
self.api._save_autoinstall_cache(games)
|
||||
self.api._autoinstall_cache = games
|
||||
self.finished.emit(games)
|
||||
|
||||
worker = AutoinstallWorker()
|
||||
worker.api = self
|
||||
worker.portproton_location = self.portproton_location
|
||||
worker.finished.connect(lambda games: callback(games))
|
||||
worker.start()
|
||||
logger.info("Started background load of autoinstall games")
|
||||
return worker
|
||||
|
||||
def _load_topics_data(self):
|
||||
"""Load and cache linux_gaming_topics_min.json from the archive."""
|
||||
if self._topics_data is not None:
|
||||
|
||||
49
portprotonqt/preloader.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import time
|
||||
|
||||
from PySide6.QtCore import QRect
|
||||
from PySide6.QtGui import QPainter, QPen, QBrush, Qt, QColor, QConicalGradient
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
class Preloader(QWidget):
|
||||
def __init__(self, speed=180.0, line_line_width=20, color=QColor(0, 120, 215), parent=None):
|
||||
super().__init__(parent)
|
||||
self.setFixedSize(150, 150)
|
||||
self._speed = speed
|
||||
self._line_width = line_line_width
|
||||
self._color1 = color
|
||||
self._color2 = QColor(color.red(), color.green(), color.blue(), 0)
|
||||
self._start_time = time.time()
|
||||
|
||||
def showEvent(self, event):
|
||||
self._start_time = time.time()
|
||||
|
||||
def paintEvent(self, event):
|
||||
rect = self._get_preloader_rect()
|
||||
center = rect.center()
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
painter.setPen(self._get_pen())
|
||||
painter.translate(center)
|
||||
painter.rotate(self._get_angle())
|
||||
painter.translate(-center)
|
||||
painter.drawArc(rect, 0, 270 * 16)
|
||||
self.update()
|
||||
|
||||
def _get_pen(self) -> QPen:
|
||||
gradient = QConicalGradient()
|
||||
gradient.setCenter(self.rect().center())
|
||||
gradient.setColorAt(0, self._color1)
|
||||
gradient.setColorAt(1, self._color2)
|
||||
pen = QPen(QBrush(gradient), self._line_width)
|
||||
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
||||
return pen
|
||||
|
||||
def _get_angle(self) -> float:
|
||||
duration = time.time() - self._start_time
|
||||
return (self._speed * duration) % 360.0
|
||||
|
||||
def _get_preloader_rect(self) -> QRect:
|
||||
size = self._line_width // 2
|
||||
rect = self.rect()
|
||||
rect.adjust(size, size, -size, -size)
|
||||
return rect
|
||||
@@ -13,7 +13,7 @@ from portprotonqt.logger import get_logger
|
||||
from portprotonqt.localization import get_steam_language
|
||||
from portprotonqt.downloader import Downloader
|
||||
from portprotonqt.dialogs import generate_thumbnail
|
||||
from portprotonqt.config_utils import get_portproton_location
|
||||
from portprotonqt.config_utils import get_portproton_location, get_portproton_start_command
|
||||
from collections.abc import Callable
|
||||
import re
|
||||
import shutil
|
||||
@@ -23,6 +23,7 @@ import requests
|
||||
import random
|
||||
import base64
|
||||
import glob
|
||||
import urllib.parse
|
||||
|
||||
downloader = Downloader()
|
||||
logger = get_logger(__name__)
|
||||
@@ -211,14 +212,28 @@ def normalize_name(s):
|
||||
|
||||
def is_valid_candidate(candidate):
|
||||
"""
|
||||
Checks if a candidate contains forbidden substrings:
|
||||
- win32
|
||||
- win64
|
||||
- gamelauncher
|
||||
Additionally checks the string without spaces.
|
||||
Returns True if the candidate is valid, otherwise False.
|
||||
Determines whether a given candidate string is valid for use as a game name.
|
||||
|
||||
The function performs the following checks:
|
||||
1. Normalizes the candidate using `normalize_name()`.
|
||||
2. Rejects the candidate if the normalized name is exactly "game"
|
||||
(to avoid overly generic names).
|
||||
3. Removes spaces and checks for forbidden substrings:
|
||||
- "win32"
|
||||
- "win64"
|
||||
- "gamelauncher"
|
||||
These are checked in the space-free version of the string.
|
||||
4. Returns True only if none of the forbidden conditions are met.
|
||||
|
||||
Args:
|
||||
candidate (str): The candidate string to validate.
|
||||
|
||||
Returns:
|
||||
bool: True if the candidate is valid, False otherwise.
|
||||
"""
|
||||
normalized_candidate = normalize_name(candidate)
|
||||
if normalized_candidate == "game":
|
||||
return False
|
||||
normalized_no_space = normalized_candidate.replace(" ", "")
|
||||
forbidden = ["win32", "win64", "gamelauncher"]
|
||||
for token in forbidden:
|
||||
@@ -397,6 +412,39 @@ def save_app_details(app_id, data):
|
||||
with open(cache_file, "wb") as f:
|
||||
f.write(orjson.dumps(data))
|
||||
|
||||
def fetch_sgdb_cover(game_name: str) -> str:
|
||||
"""
|
||||
Fetch a cover image URL from steamgrid.usebottles.com for the given game.
|
||||
The API returns a single string (quoted URL).
|
||||
"""
|
||||
try:
|
||||
encoded = urllib.parse.quote(game_name)
|
||||
url = f"https://steamgrid.usebottles.com/api/search/{encoded}"
|
||||
resp = requests.get(url, timeout=5)
|
||||
if resp.status_code != 200:
|
||||
logger.warning("SGDB request failed for %s: %s", game_name, resp.status_code)
|
||||
return ""
|
||||
text = resp.text.strip()
|
||||
# Убираем возможные кавычки вокруг строки
|
||||
if text.startswith('"') and text.endswith('"'):
|
||||
text = text[1:-1]
|
||||
if text:
|
||||
logger.info("Fetched SGDB cover for %s: %s", game_name, text)
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.warning("Failed to fetch SGDB cover for %s: %s", game_name, e)
|
||||
return ""
|
||||
|
||||
|
||||
def check_url_exists(url: str) -> bool:
|
||||
"""Check whether a URL returns HTTP 200."""
|
||||
try:
|
||||
r = requests.head(url, timeout=3)
|
||||
return r.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def fetch_app_info_async(app_id: int, callback: Callable[[dict | None], None]):
|
||||
"""
|
||||
Asynchronously fetches detailed app info from Steam API.
|
||||
@@ -615,6 +663,11 @@ def get_full_steam_game_info_async(appid: int, callback: Callable[[dict], None])
|
||||
title = decode_text(app_info.get("name", ""))
|
||||
description = decode_text(app_info.get("short_description", ""))
|
||||
cover = f"https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900_2x.jpg"
|
||||
if not check_url_exists(cover):
|
||||
logger.info("Steam cover not found for %s, trying SGDB", title)
|
||||
alt_cover = fetch_sgdb_cover(title)
|
||||
if alt_cover:
|
||||
cover = alt_cover
|
||||
|
||||
def on_protondb_tier(tier: str):
|
||||
def on_anticheat_status(anticheat_status: str):
|
||||
@@ -708,12 +761,15 @@ def get_steam_game_info_async(desktop_name: str, exec_line: str, callback: Calla
|
||||
game_name = desktop_name or exe_name.capitalize()
|
||||
|
||||
if not matching_app:
|
||||
cover = fetch_sgdb_cover(game_name) or ""
|
||||
logger.info("Using SGDB cover for non-Steam game '%s': %s", game_name, cover)
|
||||
|
||||
def on_anticheat_status(anticheat_status: str):
|
||||
callback({
|
||||
"appid": "",
|
||||
"name": decode_text(game_name),
|
||||
"description": "",
|
||||
"cover": "",
|
||||
"cover": cover,
|
||||
"controller_support": "",
|
||||
"protondb_tier": "",
|
||||
"steam_game": "false",
|
||||
@@ -744,6 +800,11 @@ def get_steam_game_info_async(desktop_name: str, exec_line: str, callback: Calla
|
||||
title = decode_text(app_info.get("name", game_name))
|
||||
description = decode_text(app_info.get("short_description", ""))
|
||||
cover = f"https://steamcdn-a.akamaihd.net/steam/apps/{appid}/library_600x900_2x.jpg"
|
||||
if not check_url_exists(cover):
|
||||
logger.info("Steam cover not found for %s, trying SGDB", title)
|
||||
alt_cover = fetch_sgdb_cover(title)
|
||||
if alt_cover:
|
||||
cover = alt_cover
|
||||
controller_support = app_info.get("controller_support", "")
|
||||
|
||||
def on_protondb_tier(tier: str):
|
||||
@@ -943,7 +1004,8 @@ def add_to_steam(game_name: str, exec_line: str, cover_path: str) -> tuple[bool,
|
||||
return (False, f"Executable file not found: {exe_path}")
|
||||
|
||||
portproton_dir = get_portproton_location()
|
||||
if not portproton_dir:
|
||||
start_sh = get_portproton_start_command()
|
||||
if not portproton_dir or not start_sh:
|
||||
logger.error("PortProton directory not found")
|
||||
return (False, "PortProton directory not found")
|
||||
|
||||
@@ -952,17 +1014,12 @@ def add_to_steam(game_name: str, exec_line: str, cover_path: str) -> tuple[bool,
|
||||
|
||||
safe_game_name = re.sub(r'[<>:"/\\|?*]', '_', game_name.strip())
|
||||
script_path = os.path.join(steam_scripts_dir, f"{safe_game_name}.sh")
|
||||
start_sh_path = os.path.join(portproton_dir, "data", "scripts", "start.sh")
|
||||
|
||||
if not os.path.exists(start_sh_path):
|
||||
logger.error(f"start.sh not found at {start_sh_path}")
|
||||
return (False, f"start.sh not found at {start_sh_path}")
|
||||
|
||||
if not os.path.exists(script_path):
|
||||
script_content = f"""#!/usr/bin/env bash
|
||||
export LD_PRELOAD=
|
||||
export START_FROM_STEAM=1
|
||||
"{start_sh_path}" "{exe_path}" "$@"
|
||||
"{start_sh}" "{exe_path}" "$@"
|
||||
"""
|
||||
try:
|
||||
with open(script_path, "w", encoding="utf-8") as f:
|
||||
|
||||
1
portprotonqt/themes/standart/images/icons/settings.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m8.0005 1c-0.38761 0-0.77522 0.0327-1.1588 0.0979-0.16351 0.0281-0.30273 0.13627-0.37209 0.28935l-0.39088 0.86264c-0.49378 0.16682-0.96454 0.39759-1.4007 0.68616 2.5e-4 0-0.90672-0.2272-0.90672-0.2272-0.161-0.0403-0.33098 3e-3 -0.45442 0.11569-0.57867 0.5285-1.0672 1.1514-1.4451 1.8432-0.0804 0.14721-0.0841 0.32549-0.01 0.47628l0.41938 0.84865c-0.17954 0.49666-0.29567 1.0147-0.346 1.5417l-0.73995 0.57946c-0.13121 0.10289-0.20407 0.26514-0.19431 0.4335 0.0453 0.78981 0.21961 1.5666 0.51558 2.2983 0.0631 0.15587 0.1978 0.27003 0.36005 0.30467l0.91397 0.19559c0.26993 0.45234 0.59572 0.86802 0.96931 1.2363l-0.0161 0.94973c-3e-3 0.16861 0.0766 0.32755 0.21183 0.42484 0.63551 0.45642 1.3414 0.80207 2.0884 1.0229 0.15926 0.0471 0.33077 0.0109 0.45872-0.0963l0.72016-0.60485c0.51582 0.0674 1.0384 0.0674 1.5544 0l0.72016 0.60485c0.12796 0.10722 0.29946 0.14343 0.45872 0.0963 0.74693-0.22083 1.4528-0.56648 2.0883-1.0229 0.13521-0.0973 0.21465-0.25623 0.21189-0.42484l-0.0161-0.94973c0.37359-0.36829 0.69939-0.78372 0.96932-1.2363l0.91396-0.19559c0.16226-0.0347 0.29695-0.1488 0.36005-0.30467 0.29597-0.73174 0.47026-1.5085 0.51558-2.2983 0.01-0.16836-0.0631-0.33061-0.1943-0.4335l-0.73996-0.57946c-0.0501-0.52671-0.16652-1.045-0.34606-1.5417l0.41944-0.84865c0.0746-0.15079 0.0709-0.32907-0.01-0.47628-0.37785-0.69176-0.86638-1.3147-1.445-1.8432-0.12345-0.11258-0.29343-0.15594-0.45443-0.11569l-0.90697 0.2272c-0.43594-0.28857-0.9067-0.51908-1.4005-0.68616l-0.39088-0.86264c-0.0694-0.15308-0.20858-0.26132-0.37209-0.28935-0.38361-0.0653-0.77121-0.0979-1.1588-0.0979zm0 4.1365a2.8152 2.8635 0 0 1 2.8152 2.8636 2.8152 2.8635 0 0 1-2.8152 2.8635 2.8152 2.8635 0 0 1-2.8152-2.8635 2.8152 2.8635 0 0 1 2.8152-2.8636z" fill="#fff" stroke-width=".25254"/></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 880 B |
1
portprotonqt/themes/standart/images/key_backspace.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g><rect x="1" y="6" width="46" height="36" rx="5" ry="5" fill="#3f424d" stroke-width="1.1506"/><rect x="4.2329" y="8.5301" width="39.534" height="30.94" rx="4.2972" ry="4.2972" fill="#fff" stroke-width=".98888"/><path d="m23.24 22.785c-0.67917 0.69059-0.67818 1.807 0 2.4913l8.0309 8.1037c1.8756 1.8787 4.6892-0.93962 2.8136-2.8183l-3.5038-3.5097c-0.58434-0.58533-0.39618-1.0598 0.44066-1.0598h9.6139c1.0992 0 1.9895-0.89179 1.9895-1.9928 0-1.1005-0.89028-1.9928-1.9895-1.9928h-9.6139c-0.82771 0-1.0277-0.47176-0.44066-1.0597l3.5038-3.5093c1.8756-1.8787-0.93803-4.6971-2.8136-2.8183z" fill="#3f424d" fill-rule="evenodd"/></g></svg>
|
||||
|
After Width: | Height: | Size: 751 B |
|
Before Width: | Height: | Size: 2.0 KiB |
48
portprotonqt/themes/standart/images/key_context.svg
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="48"
|
||||
height="48"
|
||||
version="1.1"
|
||||
viewBox="0 0 48 48"
|
||||
xml:space="preserve"
|
||||
id="svg2"
|
||||
sodipodi:docname="key_context.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs2" /><sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="8.6915209"
|
||||
inkscape:cx="72.311855"
|
||||
inkscape:cy="22.780823"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1406"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" /><path
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#686e7e;fill-opacity:1;stroke-width:0.554217;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 17.400964,38.281601 -0.04068,-15.381724 c -0.0087,-3.288656 2.401967,-6.020242 5.542168,-6.550475 V 7.4098472 C 11.174091,7.9874382 1.8422139,17.678792 1.8422139,29.550445 v 8.911269 c 3.429133,2.844892 11.5678151,2.890776 15.5587501,-0.180113 z"
|
||||
id="path10"
|
||||
sodipodi:nodetypes="csccscc" /><path
|
||||
fill="#000000"
|
||||
d="m 23.956256,40.5905 h -9e-6 c -2.438553,0 -4.433731,-1.995178 -4.433731,-4.43373 V 25.072424 c 0,-2.438552 1.995178,-4.433731 4.433731,-4.433731 h 9e-6 c 2.438552,0 4.43373,1.995179 4.43373,4.433731 V 36.15677 c 0,2.438552 -1.995178,4.43373 -4.43373,4.43373 z"
|
||||
id="path2"
|
||||
style="fill:#686e7e;fill-opacity:1;stroke-width:0.554217" /><g
|
||||
id="g15"
|
||||
transform="matrix(0.97480136,0,0,0.99852328,1.4840752,1.6593149)"><path
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#ffffff;stroke-width:0.95333;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 30.231637,35.990171 0.03878,-14.663865 c 0.0083,-3.135176 -2.289868,-5.73928 -5.283518,-6.244767 V 6.5591888 C 36.167905,7.1098239 45.209208,16.349815 45.064267,27.666494 l -0.109685,8.563937 c -3.269097,2.712122 -10.918265,2.687312 -14.722945,-0.24026 z"
|
||||
id="path14" /><path
|
||||
style="baseline-shift:baseline;display:inline;overflow:visible;vector-effect:none;fill:#686e7e;fill-opacity:1;stroke-width:0.95333;enable-background:accumulate;stop-color:#000000"
|
||||
d="m 24.224126,5.7586892 v 9.9671448 l 0.634933,0.107994 c 2.632815,0.444559 4.656653,2.729598 4.649348,5.490959 l -0.04096,15.03916 0.299778,0.230885 c 2.097287,1.613791 5.093143,2.357986 8.017658,2.392636 2.924514,0.03465 5.796042,-0.625772 7.656435,-2.169199 l 0.271848,-0.2253 0.113581,-8.91699 C 45.976953,15.94787 36.604257,6.3680498 25.024774,5.7977906 Z m 1.524956,1.6795 C 36.150995,8.3658717 44.437912,17.028984 44.301786,27.65736 l -0.104271,8.114479 c -1.445908,1.069255 -3.851487,1.720797 -6.394017,1.690673 -2.543438,-0.03013 -5.090881,-0.734663 -6.807375,-1.934591 l 0.03724,-14.199409 c 0.0087,-3.271088 -2.263607,-5.953645 -5.284281,-6.771998 z"
|
||||
id="path15" /></g></svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 874 B |
1
portprotonqt/themes/standart/images/key_e.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m12 6c-3.3238 0-5.9998 2.6763-5.9998 5.9998v24c0 3.3238 2.6763 5.9998 5.9998 5.9998h24c3.3238 0 5.9998-2.6763 5.9998-5.9998v-24c0-3.3238-2.6763-5.9998-5.9998-5.9998z" fill="#3f424d" stroke-width="2.5714"/><path d="m13.697 8.5452c-2.8537 0-5.1515 2.2979-5.1515 5.1515v20.606c0 2.8537 2.2979 5.1515 5.1515 5.1515h20.606c2.8537 0 5.1515-2.2979 5.1515-5.1515v-20.606c0-2.8537-2.2979-5.1515-5.1515-5.1515z" fill="#fff" stroke-width="2.2078"/><path d="m17.977 16.26h11.807v2.6476h-8.086v3.554h7.2989v2.6476h-7.2989v3.9834h8.3245v2.6476h-12.046z" fill="#3f424d" stroke-width=".4977" aria-label="E"/></svg>
|
||||
|
After Width: | Height: | Size: 726 B |
|
Before Width: | Height: | Size: 1.3 KiB |
1
portprotonqt/themes/standart/images/key_enter.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m6 6h36c2.77 0 5 2.23 5 5v26c0 2.77-2.23 5-5 5h-36c-2.77 0-5-2.23-5-5v-26c0-2.77 2.23-5 5-5z" fill="#3f424d" stroke-width="1.1506"/><path d="m8.5301 8.5301h30.94c2.3806 0 4.2972 1.9166 4.2972 4.2972v22.346c0 2.3806-1.9166 4.2972-4.2972 4.2972h-30.94c-2.3806 0-4.2972-1.9166-4.2972-4.2972v-22.346c0-2.3806 1.9166-4.2972 4.2972-4.2972z" fill="#fff" stroke-width=".98888"/><path d="m8.2952 18.538h8.3321v1.8684h-5.7063v2.5081h5.1508v1.8684h-5.1508v2.811h5.8746v1.8684h-8.5005zm10.268 0h2.6596l5.2854 7.4568v-7.4568h2.3397v10.924h-2.6596l-5.2854-7.5747v7.5747h-2.3397zm15.166 1.8684h-3.3665v-1.8684h9.3421v1.8684h-3.3497v9.0559h-2.6259z" fill="#3f424d" stroke-width=".35123" aria-label="ENT"/></svg>
|
||||
|
After Width: | Height: | Size: 823 B |
|
Before Width: | Height: | Size: 943 B |
1
portprotonqt/themes/standart/images/key_f11.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m12 6c-3.3238 0-5.9998 2.6763-5.9998 5.9998v24c0 3.3238 2.6763 5.9998 5.9998 5.9998h24c3.3238 0 5.9998-2.6763 5.9998-5.9998v-24c0-3.3238-2.6763-5.9998-5.9998-5.9998z" fill="#3f424d" stroke-width="2.5714"/><path d="m13.697 8.5452c-2.8537 0-5.1515 2.2979-5.1515 5.1515v20.606c0 2.8537 2.2979 5.1515 5.1515 5.1515h20.606c2.8537 0 5.1515-2.2979 5.1515-5.1515v-20.606c0-2.8537-2.2979-5.1515-5.1515-5.1515z" fill="#fff" stroke-width="2.2078"/><path d="m11.139 18.538h8.5005v1.8684h-5.8746v2.6764h5.3191v1.8684h-5.3191v4.5111h-2.6259zm13.5 2.5754-2.9794 1.6496v-2.0704l3.4507-2.1546h1.9862v10.924h-2.4576zm9.7629 0-2.9794 1.6496v-2.0704l3.4507-2.1546h1.9862v10.924h-2.4576z" fill="#3f424d" stroke-width=".35123" aria-label="F11"/></svg>
|
||||
|
After Width: | Height: | Size: 857 B |
|
Before Width: | Height: | Size: 933 B |
1
portprotonqt/themes/standart/images/key_left.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;}</style></defs><path d="m12 6c-3.3238 0-5.9998 2.6763-5.9998 5.9998v24c0 3.3238 2.6763 5.9998 5.9998 5.9998h24c3.3238 0 5.9998-2.6763 5.9998-5.9998v-24c0-3.3238-2.6763-5.9998-5.9998-5.9998z" fill="#3f424d" stroke-width="2.5714"/><path d="m13.697 8.5452c-2.8537 0-5.1515 2.2979-5.1515 5.1515v20.606c0 2.8537 2.2979 5.1515 5.1515 5.1515h20.606c2.8537 0 5.1515-2.2979 5.1515-5.1515v-20.606c0-2.8537-2.2979-5.1515-5.1515-5.1515z" fill="#fff" stroke-width="2.2078"/><path d="m26.619 34a1.9874 1.9874 0 0 1-1.3812-0.55623l-7.5143-7.2497a3.0457 3.0457 0 0 1 0-4.3873l7.5143-7.2497a1.9882 1.9882 0 0 1 2.7603 2.8624l-6.8226 6.581 6.8226 6.581a1.9874 1.9874 0 0 1-1.3791 3.4186z" fill="#3f424d" stroke-width=".20832"/></svg>
|
||||
|
After Width: | Height: | Size: 865 B |
|
Before Width: | Height: | Size: 956 B |
1
portprotonqt/themes/standart/images/key_right.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:none;}</style></defs><path d="m12 6c-3.3238 0-5.9998 2.6763-5.9998 5.9998v24c0 3.3238 2.6763 5.9998 5.9998 5.9998h24c3.3238 0 5.9998-2.6763 5.9998-5.9998v-24c0-3.3238-2.6763-5.9998-5.9998-5.9998z" fill="#3f424d" stroke-width="2.5714"/><path d="m13.697 8.5452c-2.8537 0-5.1515 2.2979-5.1515 5.1515v20.606c0 2.8537 2.2979 5.1515 5.1515 5.1515h20.606c2.8537 0 5.1515-2.2979 5.1515-5.1515v-20.606c0-2.8537-2.2979-5.1515-5.1515-5.1515z" fill="#fff" stroke-width="2.2078"/><path d="m20.778 34a1.9874 1.9874 0 0 0 1.3812-0.55623l7.5143-7.2497a3.0457 3.0457 0 0 0 0-4.3873l-7.5143-7.2497a1.9882 1.9882 0 0 0-2.7603 2.8624l6.8226 6.581-6.8226 6.581a1.9874 1.9874 0 0 0 1.3791 3.4186z" fill="#3f424d" stroke-width=".20832"/></svg>
|
||||
|
After Width: | Height: | Size: 864 B |
|
Before Width: | Height: | Size: 1.9 KiB |
1
portprotonqt/themes/standart/images/ps_circle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m47 24a23 23 0 0 1-23 23 23 23 0 0 1-23-23 23 23 0 0 1 23-23 23 23 0 0 1 23 23z" fill="#3f424d"/><path d="M 42.513,24 A 18.513,18.513 0 0 1 24,42.513 18.513,18.513 0 0 1 5.4869995,24 18.513,18.513 0 0 1 24,5.4869995 18.513,18.513 0 0 1 42.513,24 Z" fill="#fff"/><path d="m24 13.476c-5.7918 0-10.524 4.7162-10.524 10.524 0 5.7918 4.7162 10.524 10.524 10.524 5.7918 0 10.524-4.7162 10.524-10.524 0-5.7918-4.7162-10.524-10.524-10.524zm0 18.037c-4.137 0-7.5128-3.3758-7.5128-7.5128s3.3758-7.5128 7.5128-7.5128 7.5128 3.3758 7.5128 7.5128-3.3592 7.5128-7.5128 7.5128z" fill="#3f424d" stroke-width="1.6548"/></svg>
|
||||
|
After Width: | Height: | Size: 736 B |
|
Before Width: | Height: | Size: 1.7 KiB |
1
portprotonqt/themes/standart/images/ps_cross.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m47 24a23 23 0 0 1-23 23 23 23 0 0 1-23-23 23 23 0 0 1 23-23 23 23 0 0 1 23 23z" fill="#3f424d"/><path d="M 42.513,24 A 18.513,18.513 0 0 1 24,42.513 18.513,18.513 0 0 1 5.4869995,24 18.513,18.513 0 0 1 24,5.4869995 18.513,18.513 0 0 1 42.513,24 Z" fill="#fff"/><path d="m34.076 13.91c-0.57906-0.57906-1.5387-0.57906-2.1177 0l-7.958 7.958-7.958-7.958c-0.57906-0.57906-1.5387-0.57906-2.1177 0-0.57906 0.57906-0.57906 1.5387 0 2.1177l7.958 7.958-7.958 7.958c-0.57906 0.57906-0.57906 1.5387 0 2.1177 0.2978 0.2978 0.67833 0.44671 1.0589 0.44671 0.38053 0 0.76106-0.1489 1.0589-0.44671l7.958-7.9415 7.958 7.958c0.2978 0.2978 0.67833 0.44671 1.0589 0.44671s0.76106-0.1489 1.0589-0.44671c0.57906-0.57906 0.57906-1.5387 0-2.1177l-7.958-7.958 7.958-7.958c0.57906-0.59561 0.57906-1.5387 0-2.1343z" fill="#3f424d" stroke-width="1.6545"/></svg>
|
||||
|
After Width: | Height: | Size: 961 B |
|
Before Width: | Height: | Size: 1.7 KiB |
1
portprotonqt/themes/standart/images/ps_l1.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m10.465 39.437c4.1391 1.4258 20.596 4.9156 31.79 2.551 2.7034-0.57104 4.7508-3.32 4.744-6.0831l-0.057386-23.467c-0.009676-3.9677-4.6895-7.2319-7.5124-7.2255-12.075 0.0276-22.278-0.0068827-33.557 1.5493-2.7371 0.37765-4.8753 4.0033-4.8727 6.7663l0.016807 17.988c0.00451 4.8315 6.0288 6.743 9.4485 7.921z" fill="#3f424d" stroke-width="1.1477"/><path d="m12.394 37.236c3.5492 1.2226 17.661 4.2149 27.259 2.1874 2.3181-0.48964 4.0736-2.8468 4.0678-5.216l-0.049207-20.123c-0.008279-3.4022-4.0211-6.2011-6.4416-6.1956-10.354 0.023666-19.103-0.0059052-28.774 1.3285-2.347 0.32383-4.1804 3.4327-4.1782 5.802l0.014412 15.424c0.00387 4.1428 5.1694 5.7819 8.1017 6.792z" fill="#fff" stroke-width=".98413"/><path d="m13.833 16.812h3.4556v11.917h7.0662v2.4588h-10.522zm17.101 3.3891-3.9207 2.1708v-2.7246l4.541-2.8353h2.6138v14.376h-3.234z" fill="#3f424d" stroke-width=".4622" aria-label="L1"/></svg>
|
||||
|
After Width: | Height: | Size: 1015 B |
|
Before Width: | Height: | Size: 1.3 KiB |
1
portprotonqt/themes/standart/images/ps_options.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m18.047 46.216-2.1e-5 -5e-6c-5.4306-1.4551-8.6833-7.089-7.2282-12.52l6.6143-24.685c1.4551-5.4306 7.089-8.6833 12.52-7.2282l2.1e-5 5.5e-6c5.4306 1.4551 8.6833 7.089 7.2282 12.52l-6.6143 24.685c-1.4551 5.4306-7.089 8.6833-12.52 7.2282z" fill="#3f424d" stroke-width="1.2778"/><path d="m19.229 41.807-1.7e-5 -4e-6c-4.3529-1.1664-6.9601-5.6821-5.7937-10.035l5.3016-19.786c1.1664-4.3529 5.6821-6.9601 10.035-5.7937l1.7e-5 4.4e-6c4.3529 1.1664 6.9601 5.6821 5.7937 10.035l-5.3016 19.786c-1.1664 4.3529-5.6821 6.9601-10.035 5.7937z" fill="#fff" stroke-width="1.0242"/><path d="m19.502 18.291c-0.854 0-1.547 0.49867-1.547 1.114 0 0.6153 0.69299 1.114 1.547 1.114h8.997c0.854 0 1.5459-0.49867 1.5459-1.114 0-0.6153-0.69187-1.114-1.5459-1.114zm0 4.595c-0.854 0-1.547 0.49867-1.547 1.114 0 0.6153 0.69299 1.114 1.547 1.114h8.997c0.854 0 1.5459-0.49867 1.5459-1.114 0-0.6153-0.69187-1.114-1.5459-1.114zm0 4.595c-0.854 0-1.547 0.49867-1.547 1.114s0.69299 1.114 1.547 1.114h8.997c0.854 0 1.5459-0.49867 1.5459-1.114s-0.69187-1.114-1.5459-1.114z" fill="#3f424d" fill-rule="evenodd" stroke-width=".11455"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
1
portprotonqt/themes/standart/images/ps_r1.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m37.535 39.437c-4.1391 1.4258-20.596 4.9156-31.79 2.551-2.7034-0.57104-4.7508-3.32-4.744-6.0831l0.057386-23.467c0.00968-3.9677 4.6895-7.2319 7.5124-7.2255 12.075 0.0276 22.278-0.00688 33.557 1.5493 2.7371 0.37765 4.8753 4.0033 4.8727 6.7663l-0.01681 17.988c-0.0045 4.8315-6.0288 6.743-9.4485 7.921z" fill="#3f424d" stroke-width="1.1477"/><path d="m35.606 37.236c-3.5492 1.2226-17.661 4.2149-27.259 2.1874-2.3181-0.48964-4.0736-2.8468-4.0678-5.216l0.049207-20.123c0.00828-3.4022 4.0211-6.2011 6.4416-6.1956 10.354 0.023666 19.103-0.00591 28.774 1.3285 2.347 0.32383 4.1804 3.4327 4.1782 5.802l-0.01441 15.424c-0.0039 4.1428-5.1694 5.7819-8.1017 6.792z" fill="#fff" stroke-width=".98413"/><path d="m12.858 16.812h6.4681q2.8796 0 4.1644 0.70883 1.2848 0.68668 1.2848 2.3259v2.5252q0 1.2626-0.90819 1.9936-0.88604 0.70883-2.3702 0.90819l4.1644 5.9143h-3.9872l-3.7657-5.6485h-1.5949v5.6485h-3.4556zm6.4238 6.4459q1.2183 0 1.6613-0.31011 0.44302-0.33226 0.44302-1.2626v-1.0189q0-0.79744-0.48732-1.0854-0.46517-0.31011-1.617-0.31011h-2.9682v3.9872zm12.626-3.0568-3.9207 2.1708v-2.7246l4.541-2.8353h2.6138v14.376h-3.234z" fill="#3f424d" stroke-width=".4622" aria-label="R1"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
1
portprotonqt/themes/standart/images/ps_share.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m29.953 46.216 2.1e-5 -5e-6c5.4306-1.4551 8.6833-7.089 7.2282-12.52l-6.6143-24.685c-1.4551-5.4306-7.089-8.6833-12.52-7.2282l-2.1e-5 5.5e-6c-5.4306 1.4551-8.6833 7.089-7.2282 12.52l6.6143 24.685c1.4551 5.4306 7.089 8.6833 12.52 7.2282z" fill="#3f424d" stroke-width="1.2778"/><path d="m28.771 41.807 1.7e-5 -4e-6c4.3529-1.1664 6.9601-5.6821 5.7937-10.035l-5.3016-19.786c-1.1664-4.3529-5.6821-6.9601-10.035-5.7937l-1.7e-5 4.4e-6c-4.3529 1.1664-6.9601 5.6821-5.7937 10.035l5.3016 19.786c1.1664 4.3529 5.6821 6.9601 10.035 5.7937z" fill="#fff" stroke-width="1.0242"/><path d="m24.034 20.416c-0.54232 0-0.98296 0.41005-0.98296 0.91636v5.3348c0 0.50632 0.44064 0.91636 0.98296 0.91636s0.98124-0.41005 0.98124-0.91636v-5.3348c0-0.50632-0.43892-0.91636-0.98124-0.91636zm-5.9615 0.72033c-0.15955 0.0017-0.31975 0.03855-0.46652 0.11513-0.46966 0.24506-0.62269 0.79993-0.34257 1.2384l2.9506 4.6191c0.28012 0.43848 0.88858 0.59512 1.3582 0.35005 0.46966-0.24506 0.62269-0.79837 0.34257-1.2369l-2.9506-4.6192c-0.19258-0.30146-0.5407-0.4705-0.89172-0.46674zm11.856 0c-0.35102-0.0037-0.69914 0.16528-0.89172 0.46674l-2.9506 4.6191c-0.28011 0.43848-0.12709 0.99179 0.34257 1.2369 0.46967 0.24506 1.0781 0.08843 1.3582-0.35005l2.9506-4.6191c0.28011-0.43848 0.12709-0.99335-0.34257-1.2384-0.14677-0.07658-0.30696-0.11342-0.46652-0.11513z" fill="#3f424d" fill-rule="evenodd" stroke-width=".082805"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1
portprotonqt/themes/standart/images/ps_square.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m47 24a23 23 0 0 1-23 23 23 23 0 0 1-23-23 23 23 0 0 1 23-23 23 23 0 0 1 23 23z" fill="#3f424d"/><path d="M 42.513,24 A 18.513,18.513 0 0 1 24,42.513 18.513,18.513 0 0 1 5.4869995,24 18.513,18.513 0 0 1 24,5.4869995 18.513,18.513 0 0 1 42.513,24 Z" fill="#fff"/><path d="m33.019 13.476h-18.037c-0.8274 0-1.5059 0.67847-1.5059 1.5059v18.037c0 0.8274 0.67847 1.5059 1.5059 1.5059h18.037c0.8274 0 1.5059-0.67847 1.5059-1.5059v-18.037c0-0.8274-0.66192-1.5059-1.5059-1.5059zm-1.4893 18.037h-15.026v-15.026h15.026z" fill="#3f424d" stroke-width="1.6548"/></svg>
|
||||
|
After Width: | Height: | Size: 682 B |
|
Before Width: | Height: | Size: 1.8 KiB |
1
portprotonqt/themes/standart/images/ps_triangle.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m47 24a23 23 0 0 1-23 23 23 23 0 0 1-23-23 23 23 0 0 1 23-23 23 23 0 0 1 23 23z" fill="#3f424d"/><path d="M 42.513,24 A 18.513,18.513 0 0 1 24,42.513 18.513,18.513 0 0 1 5.4869995,24 18.513,18.513 0 0 1 24,5.4869995 18.513,18.513 0 0 1 42.513,24 Z" fill="#fff"/><path d="m13.766 32.511h20.449c0.60033 0 1.1631-0.31892 1.4821-0.84421 0.30016-0.52529 0.30016-1.1819 0-1.7072l-10.224-17.71c-0.60033-1.0506-2.345-1.0506-2.9454 0l-10.224 17.71c-0.30016 0.52529-0.30016 1.1819 0 1.7072s0.86297 0.84421 1.4633 0.84421zm10.224-15.984 7.2602 12.588h-14.539z" fill="#3f424d" stroke-width="1.876"/></svg>
|
||||
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 232 KiB |
BIN
portprotonqt/themes/standart/images/screenshots/Библиотека.jpg
Normal file
|
After Width: | Height: | Size: 225 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
BIN
portprotonqt/themes/standart/images/screenshots/Карточка.jpg
Normal file
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 364 KiB |
|
Before Width: | Height: | Size: 430 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 1.0 MiB |
BIN
portprotonqt/themes/standart/images/screenshots/Темы.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
1
portprotonqt/themes/standart/images/xbox_a.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m47 24a23 23 0 0 1-23 23 23 23 0 0 1-23-23 23 23 0 0 1 23-23 23 23 0 0 1 23 23z" fill="#3f424d"/><path d="M 42.513,24 A 18.513,18.513 0 0 1 24,42.513 18.513,18.513 0 0 1 5.4869995,24 18.513,18.513 0 0 1 24,5.4869995 18.513,18.513 0 0 1 42.513,24 Z" fill="#fff"/><path d="m21.016 13.475h6.1623l7.5893 21.049h-5.1244l-1.8811-5.546h-7.6866l-1.8487 5.546h-4.9947zm5.6433 12.13-2.6595-7.9137h-0.12973l-2.6595 7.9137z" fill="#3f424d" stroke-width=".67675" aria-label="A"/></svg>
|
||||
|
After Width: | Height: | Size: 600 B |
|
Before Width: | Height: | Size: 1.7 KiB |
1
portprotonqt/themes/standart/images/xbox_b.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m47 24a23 23 0 0 1-23 23 23 23 0 0 1-23-23 23 23 0 0 1 23-23 23 23 0 0 1 23 23z" fill="#3f424d"/><path d="M 42.513,24 A 18.513,18.513 0 0 1 24,42.513 18.513,18.513 0 0 1 5.4869995,24 18.513,18.513 0 0 1 24,5.4869995 18.513,18.513 0 0 1 42.513,24 Z" fill="#fff"/><path d="m15.973 13.476h8.5299q3.0163 0 4.6379 0.45406 1.6541 0.42163 2.3352 1.3946 0.68109 0.94056 0.68109 2.6595v2.5946q0 0.87569-0.71353 1.6541-0.68109 0.77839-1.6216 1.0703v0.16216q1.2325 0.12973 2.2379 1.0703 1.0379 0.90812 1.0379 2.0433v3.2433q0 2.5622-2.0433 3.6325t-6.3244 1.0703h-8.7569zm8.5299 8.5623q1.2 0 1.7838-0.1946t0.77839-0.61623q0.22703-0.45406 0.22703-1.2973v-1.0379q0-0.74596-0.1946-1.1027-0.1946-0.3892-0.81082-0.55136-0.58379-0.16216-1.8811-0.16216h-3.373v4.9622zm0.12973 8.8866q1.8487 0 2.6271-0.42163t0.77839-1.3622v-1.6865q0-1.1676-0.61623-1.6541-0.58379-0.4865-2.1081-0.4865h-4.2812v5.6109z" fill="#3f424d" stroke-width=".67675" aria-label="B"/></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
1
portprotonqt/themes/standart/images/xbox_lb.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="48" height="48" version="1.1" viewBox="0 0 48 48" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m6.3645 12.915h35.271c2.9719 0 5.3645 2.3926 5.3645 5.3645v11.442c0 2.9719-2.3926 5.3645-5.3645 5.3645h-35.271c-2.9719 0-5.3645-2.3926-5.3645-5.3645v-11.442c0-2.9719 2.3926-5.3645 5.3645-5.3645z" fill="#3f424d"/><path d="m9.2118 14.704h29.576c2.4921 0 4.4983 2.0062 4.4983 4.4983v9.5944c0 2.4921-2.0062 4.4983-4.4983 4.4983h-29.576c-2.4921 0-4.4983-2.0062-4.4983-4.4983v-9.5944c0-2.4921 2.0062-4.4983 4.4983-4.4983z" fill="#fff"/><path d="m13.757 18h2.8844v9.9476h5.8983v2.0524h-8.7827zm10.724 0h4.8629q1.7196 0 2.6441 0.25886 0.94299 0.24037 1.3313 0.79507 0.38829 0.53621 0.38829 1.5162v1.4792q0 0.49923-0.40678 0.94299-0.38829 0.44376-0.9245 0.61017v0.09245q0.70262 0.07396 1.2758 0.61017 0.59168 0.51772 0.59168 1.1649v1.849q0 1.4607-1.1649 2.0709-1.1649 0.61017-3.6055 0.61017h-4.9923zm4.8629 4.8814q0.68413 0 1.0169-0.11094 0.33282-0.11094 0.44376-0.35131 0.12943-0.25886 0.12943-0.7396v-0.59168q0-0.42527-0.11094-0.62866-0.11094-0.22188-0.46225-0.31433-0.33282-0.09245-1.0724-0.09245h-1.923v2.829zm0.07396 5.0663q1.0539 0 1.4977-0.24037 0.44376-0.24037 0.44376-0.77658v-0.96148q0-0.66564-0.35131-0.94299-0.33282-0.27735-1.2018-0.27735h-2.4407v3.1988z" fill="#3f424d" stroke-width=".38581" aria-label="LB"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |