Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d0d1dff778
|
||
|
|
07e07dbe09
|
||
|
|
004916a82b
|
||
|
|
ae2115ad0a | ||
|
|
357a0dcf42
|
||
|
|
e5df48b9b0
|
||
|
|
48217b93f5
|
||
|
|
cc2372107a
|
||
|
|
a32d63f5e5
|
||
|
|
cbf29005ff
|
||
|
|
8d10bae8f2
|
||
|
|
28d0417ede
|
||
|
|
7efb0aadec
|
||
|
|
d4d7e9c044
|
||
|
|
eb18178958
|
||
|
|
979e208969
|
||
|
|
8704ba00e9
|
||
|
|
d9d8242475 | ||
|
|
64e626b2b2 | ||
|
|
8af84460eb
|
||
|
|
b7e83ad1d2
|
||
|
|
ffff6efa68
|
||
|
|
fabf14fc46
|
||
|
|
88db436b94
|
||
|
|
592d24c9c6
|
||
|
|
c472c785cc
|
||
|
|
759e931dfc
|
||
|
|
93f5ea49d4
|
||
|
|
d9c55bed61 | ||
|
|
64bbabd0f1 | ||
|
|
c3e9433f9d
|
||
|
|
c4274bbef6
|
||
|
|
cdfd983580
|
||
|
|
749113b48c
|
||
|
|
5fdba88c5c
|
||
|
|
a20887da5b
|
||
|
|
c9b8871fb1
|
||
|
|
6db27ef7af
|
||
|
|
97de53303c
|
||
|
|
07ac67e934 | ||
|
|
66e1e437f8 | ||
|
|
12c98adbd7
|
||
|
|
d7de29008e
|
||
|
|
4d99a66e84
|
||
|
|
288242195b | ||
|
|
892124efba | ||
|
|
ae7bd3e5ff | ||
|
|
042b90d663 | ||
|
|
4fdaf0115e | ||
|
|
b00f62776c
|
||
|
|
69674b5c75
|
||
|
|
eaa3467410
|
||
|
|
47a949fe6e
|
||
|
|
8902369fe7 | ||
|
|
c0529ac9fd | ||
|
|
a9ed377b00 | ||
|
|
bc523762b6 | ||
|
|
2f5159129c
|
||
|
|
eef8c7d496
|
||
|
|
64cad59d53
|
No files matched your search
@@ -8,7 +8,7 @@ on:
|
||||
|
||||
env:
|
||||
# Common version, will be used for tagging the release
|
||||
VERSION: 1.0
|
||||
VERSION: 1.1.0
|
||||
PKGDEST: "/tmp/portprotonqt"
|
||||
PACKAGE: "portprotonqt"
|
||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
jobs:
|
||||
renovate:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/renovatebot/renovate:latest@sha256:2ccc5b1f0340593c40e1598547aa98feee4e521a0906a423fe0be0431a733dfa
|
||||
container: ghcr.io/renovatebot/renovate:latest@sha256:47421f2f3f82048e969b5195dd808d5ac2c5f69d43d192191c4a3dca390afe06
|
||||
steps:
|
||||
- uses: https://gitea.com/actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
|
||||
|
||||
@@ -785,6 +785,11 @@ PortProtonQt/
|
||||
- These scripts are for developer convenience only
|
||||
- Do not refactor or improve dev-scripts unless explicitly requested
|
||||
|
||||
## PortProton Scripts
|
||||
|
||||
- In `build-aux/share/portproton/scripts/`, run `portprotonqt.scripts_utils` modules through the existing `python_module` function
|
||||
- Do not add direct `python3 -m portprotonqt.scripts_utils.*` calls where `python_module` is available
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -3,6 +3,53 @@
|
||||
Все заметные изменения в этом проекте фиксируются в этом файле.
|
||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [1.1.0] - 2026-05-26
|
||||
|
||||
### Added
|
||||
- Добавлен диалог прогресса для создания и восстановления резервных копий префиксов
|
||||
- Добавлено автосоздание ярлыков GOG игр
|
||||
- Для обложек добавлена поддержка SVG
|
||||
- Добавлена поддержка `.bat`, `.msi` и `.reg`
|
||||
- Добавлена кнопка `Быстрый запуск` в библиотеку для открытия детальной страницы любого исполняемого файла без создания ярлыка
|
||||
- Добавлена проверка доступа на чтение и запись для `PORT_DATA_PATH` при выборе и запуске
|
||||
|
||||
### Changed
|
||||
- Обезличен интерфейс: игровые термины (Игра, Игровое время, Играть и т.д.) заменены на более общие (Приложение, Время в приложении, Запуск)
|
||||
- Резервные копии `.ppack` теперь создаются через `libarchive-c` в формате `pax` с `zstd` вместо SquashFS (лучше скорость и качество сжатия)
|
||||
- При ошибке загрузки обложки Steam/SGDB теперь используется иконка из `.exe`
|
||||
- Удалены заголовки вкладок `Библиотека` и `Автоустановка`
|
||||
- Улучшена навигация по библиотеке с клавиатуры и геймпада: фокус ставится на первую карточку, а верхние кнопки доступны из первого ряда
|
||||
- Пустая библиотека теперь корректно обновляется после запуска приложения через правую кнопку мыши
|
||||
- Для тем с режимом библиотеки `list` теперь используются обложки автоустановок
|
||||
- Установка Wine/LG-сборок в `data/dist` теперь не зависит от настройки загрузки Proton в Steam
|
||||
- При сбросе настроек PortProtonQt теперь также сбрасывается `user.conf` PortProton
|
||||
- Удалён отдельный пакет `portprotonqt-steam-compat` теперь установленный через галочку режим совместимости self-update
|
||||
- D-Bus операции скриптов перенесены в `scripts_utils.dbus_tools` на базе `dbus-fast`
|
||||
- Настройка авто-перехода в полноэкранный режим при подключении геймпада перенесена в подпункт настройки полноэкранного запуска
|
||||
|
||||
### Fixed
|
||||
- Исправлена некорректная установка DPI в Wine, когда опция `Использовать системный DPI` отключена
|
||||
- Исправлено определение версии DXVK в логе, если у приложения отсутствует PPDB
|
||||
- Исправлена пустая вкладка настроек Wine при отсутствии каталога `data/dist`
|
||||
- Исправлено обновление прогресса кнопок при загрузке библиотек и зависимостей PortProton
|
||||
- Исправлен повторный запуск восстановления префикса после смены темы сразу после восстановления
|
||||
- Исправлена эмуляция мыши с геймпада после запуска приложений через лаунчеры
|
||||
- Исправлен запуск PortProtonQt в полноэкранном режиме, если геймпад подключён до старта приложения
|
||||
- Исправлено отображение префиксов `DEFAULT` и `DOTNET` при отсутствии соответствующих каталогов
|
||||
- Исправлена локализация AppImage на РОСА «ФРЕШ»
|
||||
- Исправлено обновление библиотеки после создания ярлыков PortProton
|
||||
- Исправлен запуск `exiftool` для ярлыков с отсутствующими `.exe`
|
||||
- Исправлено восстановление профиля питания после остановки приложения из трея
|
||||
- Исправлено применения языка `Китайский (упрощённый)`
|
||||
- Исправлено попадание служебных строк PortProton в список видеокарт и скрытие выбора видеокарты при одной доступной GPU
|
||||
|
||||
### Contributors
|
||||
- @Castro-Fidel
|
||||
- @Simple16
|
||||
- @Dervart
|
||||
|
||||
---
|
||||
|
||||
## [1.0] - 2026-05-23
|
||||
|
||||
### Added
|
||||
@@ -59,7 +106,7 @@
|
||||
- Улучшенно качестве иконок из `exe` файлов
|
||||
- В трей добавлены компактный и детальный режимы меню
|
||||
- Автоустановки теперь можно остановить
|
||||
- Префиксы `DEFAULT`, `DOTNET` и `PROGRAMS` теперь всегда отображаются вверху списков префиксов
|
||||
- Префиксы `DEFAULT` и `DOTNET` теперь всегда отображаются вверху списков префиксов
|
||||
- Из лога удалена версия скриптов, так как скрипты теперь внутри PortProtonQt
|
||||
- Из downloader удалена отдельная проверка сети через Yandex и Google
|
||||
- Выбор видеокарты теперь не отображается в настройках PortProtonQt если видеокарта всего одна
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
pkgname=('portprotonqt' 'portprotonqt-steam-compat')
|
||||
pkgver=1.0
|
||||
pkgname=('portprotonqt')
|
||||
pkgver=1.1.0
|
||||
pkgrel=1
|
||||
pkgdesc="Modern GUI for managing and launching games from PortProton and Steam"
|
||||
arch=('any')
|
||||
url="https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt"
|
||||
license=('GPL-3.0')
|
||||
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pygame' 'python-orjson'
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' 'squashfs-tools' '7zip' 'python-dbus-fast')
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast')
|
||||
makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext')
|
||||
conflicts=("portprotonqt-steam-compat")
|
||||
provides=('portprotonqt-steam-compat')
|
||||
replaces=('portprotonqt-steam-compat')
|
||||
source=("git+https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt#tag=v$pkgver")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
@@ -24,6 +27,7 @@ package_portprotonqt() {
|
||||
'upower: System tab bluetooth battery level'
|
||||
'libpulse: System tab audio volume/output management'
|
||||
'python-qrcode: Wi-Fi QR code generation'
|
||||
'squashfs-tools: for legacy PortProton prefix backup support'
|
||||
)
|
||||
|
||||
DESTDIR="$pkgdir" meson install -C build --skip-subprojects
|
||||
@@ -32,18 +36,4 @@ package_portprotonqt() {
|
||||
install -Dpm 0644 "$srcdir/PortProtonQt/completions/portprotonqt" -t "$pkgdir/usr/share/bash-completion/completions"
|
||||
install -Dpm 0644 "$srcdir/PortProtonQt/completions/portprotonqt.fish" -t "$pkgdir/usr/share/fish/vendor_completions.d"
|
||||
install -Dpm 0644 "$srcdir/PortProtonQt/completions/_portprotonqt" -t "$pkgdir/usr/share/zsh/site-functions"
|
||||
|
||||
# Remove steam-compat files from main package
|
||||
rm -rf "$pkgdir/usr/share/steam"
|
||||
}
|
||||
|
||||
package_portprotonqt-steam-compat() {
|
||||
pkgdesc="Steam compatibility tool for PortProtonQt"
|
||||
depends=('portprotonqt')
|
||||
|
||||
DESTDIR="$pkgdir" meson install -C build --skip-subprojects
|
||||
|
||||
# Keep only steam-compat files
|
||||
find "$pkgdir/usr" -mindepth 1 -type f -not -path "${pkgdir}/usr/share/steam*" -print0 | xargs -0 rm -f
|
||||
find "$pkgdir/usr" -mindepth 1 -type d -not -path "${pkgdir}/usr/share/steam*" -empty -delete
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
pkgname=('portprotonqt-git' 'portprotonqt-git-steam-compat')
|
||||
pkgname=('portprotonqt-git')
|
||||
pkgver=.
|
||||
pkgrel=1
|
||||
pkgdesc="Modern GUI for managing and launching games from PortProton and Steam (development build)"
|
||||
@@ -6,8 +6,11 @@ arch=('any')
|
||||
url="https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt"
|
||||
license=('GPL-3.0')
|
||||
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pygame' 'python-orjson'
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' 'squashfs-tools' '7zip' 'python-dbus-fast')
|
||||
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'python-pefile' 'python-pillow' 'perl-image-exiftool' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'jq' 'file' 'findutils' 'gawk' 'grep' 'tar' 'xz' 'zstd' 'gzip' 'unrar' 'qt6-svg' 'qt6-imageformats' 'pciutils' 'mesa-utils' 'vulkan-icd-loader' 'procps-ng' 'psmisc' '7zip' 'python-dbus-fast')
|
||||
makedepends=('meson' 'ninja' 'vulkan-headers' 'gettext')
|
||||
conflicts=("portprotonqt-steam-compat")
|
||||
provides=('portprotonqt-steam-compat')
|
||||
replaces=('portprotonqt-steam-compat')
|
||||
source=("git+https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt.git")
|
||||
sha256sums=('SKIP')
|
||||
|
||||
@@ -29,6 +32,7 @@ package_portprotonqt-git() {
|
||||
'upower: System tab bluetooth battery level'
|
||||
'libpulse: System tab audio volume/output management'
|
||||
'python-qrcode: Wi-Fi QR code generation'
|
||||
'squashfs-tools: for legacy PortProton prefix backup support'
|
||||
)
|
||||
|
||||
DESTDIR="$pkgdir" meson install -C build --skip-subprojects
|
||||
@@ -37,18 +41,4 @@ package_portprotonqt-git() {
|
||||
install -Dpm 0644 "$srcdir/PortProtonQt/completions/portprotonqt" -t "$pkgdir/usr/share/bash-completion/completions"
|
||||
install -Dpm 0644 "$srcdir/PortProtonQt/completions/portprotonqt.fish" -t "$pkgdir/usr/share/fish/vendor_completions.d"
|
||||
install -Dpm 0644 "$srcdir/PortProtonQt/completions/_portprotonqt" -t "$pkgdir/usr/share/zsh/site-functions"
|
||||
|
||||
# Remove steam-compat files from main package
|
||||
rm -rf "$pkgdir/usr/share/steam"
|
||||
}
|
||||
|
||||
package_portprotonqt-git-steam-compat() {
|
||||
pkgdesc="Steam compatibility tool for PortProtonQt (development build)"
|
||||
depends=('portprotonqt-git')
|
||||
|
||||
DESTDIR="$pkgdir" meson install -C build --skip-subprojects
|
||||
|
||||
# Keep only steam-compat files
|
||||
find "$pkgdir/usr" -mindepth 1 -type f -not -path "${pkgdir}/usr/share/steam*" -print0 | xargs -0 rm -f
|
||||
find "$pkgdir/usr" -mindepth 1 -type d -not -path "${pkgdir}/usr/share/steam*" -empty -delete
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
%global pypi_name portprotonqt
|
||||
%global pypi_version 1.0
|
||||
%global pypi_version 1.1.0
|
||||
%global oname PortProtonQt
|
||||
%global build_timestamp %(date +"%Y%m%d")
|
||||
%global _python_no_extras_requires 1
|
||||
@@ -13,7 +13,7 @@ Summary: Modern GUI for managing and launching games from PortProton and
|
||||
|
||||
License: GPL-3.0
|
||||
URL: https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt
|
||||
ExclusiveArch: x86_64 aarch64
|
||||
ExclusiveArch: x86_64
|
||||
|
||||
BuildRequires: meson >= 0.61.2
|
||||
BuildRequires: ninja-build
|
||||
@@ -63,7 +63,6 @@ Requires: pciutils
|
||||
Requires: vulkan-loader
|
||||
Requires: procps-ng
|
||||
Requires: psmisc
|
||||
Requires: squashfs-tools
|
||||
Requires: 7zip
|
||||
Requires: python3-dbus-fast
|
||||
|
||||
@@ -74,18 +73,15 @@ Recommends: upower
|
||||
Recommends: pulseaudio-utils
|
||||
Recommends: python3-qrcode
|
||||
|
||||
# For legacy PortProton prefix backup support
|
||||
Recommends: squashfs-tools
|
||||
|
||||
Obsoletes: %{pypi_name}-steam-compat < %{?epoch:%{epoch}:}%{version}-%{release}
|
||||
Provides: %{pypi_name}-steam-compat = %{?epoch:%{epoch}:}%{version}-%{release}
|
||||
|
||||
%description
|
||||
A modern and intuitive interface for managing and launching games from PortProton and Steam. Combines libraries in one place and simplifies running Windows games on Linux.
|
||||
|
||||
%package -n %{pypi_name}-git-steam-compat
|
||||
Summary: Steam compatibility tool for PortProtonQt (development build)
|
||||
License: GPL-3.0
|
||||
Requires: %{pypi_name}-git = %{version}-%{release}
|
||||
|
||||
%description -n %{pypi_name}-git-steam-compat
|
||||
Steam compatibility tool integration for PortProtonQt. This package installs
|
||||
the necessary files to use PortProtonQt as a Proton compatibility tool in Steam.
|
||||
|
||||
%{?python_disable_dependency_generator}
|
||||
|
||||
%prep
|
||||
@@ -125,7 +121,4 @@ install -Dpm 0644 ./completions/_portprotonqt -t %{buildroot}%{zsh_completions_d
|
||||
%{fish_completions_dir}/portprotonqt.fish
|
||||
%{zsh_completions_dir}/_portprotonqt
|
||||
|
||||
%files -n %{pypi_name}-git-steam-compat
|
||||
%{_datadir}/steam/compatibilitytools.d/PortProtonQt/
|
||||
|
||||
%changelog
|
||||
@@ -1,5 +1,5 @@
|
||||
%global pypi_name portprotonqt
|
||||
%global pypi_version 1.0
|
||||
%global pypi_version 1.1.0
|
||||
%global oname PortProtonQt
|
||||
%global _python_no_extras_requires 1
|
||||
|
||||
@@ -10,7 +10,7 @@ Summary: Modern GUI for managing and launching games from PortProton and
|
||||
|
||||
License: GPL-3.0
|
||||
URL: https://git.linux-gaming.ru/Linux-Gaming/PortProtonQt
|
||||
ExclusiveArch: x86_64 aarch64
|
||||
ExclusiveArch: x86_64
|
||||
|
||||
BuildRequires: meson >= 0.61.2
|
||||
BuildRequires: ninja-build
|
||||
@@ -60,7 +60,6 @@ Requires: pciutils
|
||||
Requires: vulkan-loader
|
||||
Requires: procps-ng
|
||||
Requires: psmisc
|
||||
Requires: squashfs-tools
|
||||
Requires: 7zip
|
||||
Requires: python3-dbus-fast
|
||||
|
||||
@@ -71,18 +70,15 @@ Recommends: upower
|
||||
Recommends: pulseaudio-utils
|
||||
Recommends: python3-qrcode
|
||||
|
||||
# For legacy PortProton prefix backup support
|
||||
Recommends: squashfs-tools
|
||||
|
||||
Obsoletes: %{pypi_name}-steam-compat < %{?epoch:%{epoch}:}%{version}-%{release}
|
||||
Provides: %{pypi_name}-steam-compat = %{?epoch:%{epoch}:}%{version}-%{release}
|
||||
|
||||
%description
|
||||
A modern and intuitive interface for managing and launching games from PortProton and Steam. Combines libraries in one place and simplifies running Windows games on Linux.
|
||||
|
||||
%package -n %{pypi_name}-steam-compat
|
||||
Summary: Steam compatibility tool for PortProtonQt
|
||||
License: GPL-3.0
|
||||
Requires: %{pypi_name} = %{version}-%{release}
|
||||
|
||||
%description -n %{pypi_name}-steam-compat
|
||||
Steam compatibility tool integration for PortProtonQt. This package installs
|
||||
the necessary files to use PortProtonQt as a Proton compatibility tool in Steam.
|
||||
|
||||
%{?python_disable_dependency_generator}
|
||||
|
||||
%prep
|
||||
@@ -123,7 +119,4 @@ install -Dpm 0644 ./completions/_portprotonqt -t %{buildroot}%{zsh_completions_d
|
||||
%{fish_completions_dir}/portprotonqt.fish
|
||||
%{zsh_completions_dir}/_portprotonqt
|
||||
|
||||
%files -n %{pypi_name}-steam-compat
|
||||
%{_datadir}/steam/compatibilitytools.d/PortProtonQt/
|
||||
|
||||
%changelog
|
||||
@@ -11,4 +11,4 @@ Terminal=false
|
||||
Icon=ru.linux_gaming.PortProtonQt
|
||||
StartupWMClass=ru.linux_gaming.PortProtonQt
|
||||
Categories=Game;Utility;
|
||||
MimeType=x-scheme-handler/portproton;application/x-portproton-prefix-backup;application/x-ms-dos-exec;application/x-executable;application/x-dosexec;application/x-iso9660-image;application/x-cd-image;application/x-raw-disk-image;application/x-mdf;application/x-mds-disk;
|
||||
MimeType=x-scheme-handler/portproton;application/x-portproton-prefix-backup;application/x-ms-dos-exec;application/x-ms-dos-batch;application/x-msi;text/x-ms-regedit;application/x-executable;application/x-dosexec;application/x-iso9660-image;application/x-cd-image;application/x-raw-disk-image;application/x-mdf;application/x-mds-disk;
|
||||
@@ -4,6 +4,5 @@
|
||||
<comment>PortProton prefix backup</comment>
|
||||
<comment xml:lang="ru">Резервная копия префикса PortProton</comment>
|
||||
<glob pattern="*.ppack"/>
|
||||
<sub-class-of type="application/vnd.squashfs"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
||||
@@ -4,24 +4,31 @@
|
||||
########################################################################
|
||||
$PW_DEBUG
|
||||
|
||||
process_log () {
|
||||
local filter_word="mono|gecko"
|
||||
echo "$@" | sed -r "/$filter_word/Id" >> "$PW_TMPFS_PATH/process.log"
|
||||
}
|
||||
export -f process_log
|
||||
|
||||
print_error () { printf "\E[31m%s Error: $@ %s\e[0m\n" ; process_log "$@" ;}
|
||||
print_error () { printf "\E[31m%s Error: $@ %s\e[0m\n" ;}
|
||||
export -f print_error
|
||||
|
||||
print_warning () { printf "\E[33m%s Warning: $@ %s\e[0m\n" ; process_log "$@" ;}
|
||||
print_warning () { printf "\E[33m%s Warning: $@ %s\e[0m\n" ;}
|
||||
export -f print_warning
|
||||
|
||||
print_info () { printf "\E[36m%s Info: $@ %s\e[0m\n" ; process_log "$@" ;}
|
||||
print_info () { printf "\E[36m%s Info: $@ %s\e[0m\n" ;}
|
||||
export -f print_info
|
||||
|
||||
print_ok () { printf "\E[35m%s OK: $@ %s\e[0m\n" ; process_log "$@" ;}
|
||||
print_ok () { printf "\E[35m%s OK: $@ %s\e[0m\n" ;}
|
||||
export -f print_ok
|
||||
|
||||
python_module () {
|
||||
local python3_module="$1"
|
||||
shift
|
||||
print_info "Running python3 module: $python3_module $@" >&2
|
||||
python3 -m portprotonqt.scripts_utils.$python3_module "$@"
|
||||
}
|
||||
|
||||
python_term () {
|
||||
print_info "Running: easyterm $@"
|
||||
python_module easyterm -e "$@"
|
||||
[[ "$?" == "0" ]] && return 0 || return 1
|
||||
}
|
||||
|
||||
print_wrapped () {
|
||||
local text="$1"
|
||||
local a="0"
|
||||
@@ -162,6 +169,32 @@ pw_is_process_active() {
|
||||
return 1
|
||||
}
|
||||
|
||||
pw_power_profile_state_file() {
|
||||
echo "${PW_TMPFS_PATH}/power_profile"
|
||||
}
|
||||
|
||||
pw_save_power_profile() {
|
||||
[[ -n "$1" ]] || return 1
|
||||
printf "%s\n" "$1" > "$(pw_power_profile_state_file)"
|
||||
}
|
||||
|
||||
pw_restore_power_profile() {
|
||||
local current_power_profile saved_power_profile state_file
|
||||
state_file="$(pw_power_profile_state_file)"
|
||||
saved_power_profile="$PW_POWERPROFILECTL_PROFILE"
|
||||
|
||||
if [[ -z "$saved_power_profile" && -f "$state_file" ]] ; then
|
||||
saved_power_profile="$(head -n 1 "$state_file")"
|
||||
fi
|
||||
|
||||
[[ -n "$saved_power_profile" ]] || return 0
|
||||
current_power_profile=$(python_module power_profiles get)
|
||||
if [[ "$current_power_profile" != "$saved_power_profile" ]] ; then
|
||||
python_module power_profiles set "$saved_power_profile" &>/dev/null
|
||||
fi
|
||||
rm -f "$state_file"
|
||||
}
|
||||
|
||||
try_copy_file () {
|
||||
if [[ ! -f "$1" ]] ; then print_info "file $1 not found for copy" && return 1
|
||||
elif [[ -z "$2" ]] ; then print_error "no way to copy file $1" && return 1
|
||||
@@ -462,7 +495,7 @@ try_download () {
|
||||
# set -o pipefail
|
||||
curl -f -# -A 'Mozilla/5.0 (compatible; Konqueror/2.1.1; X11)' -H 'Cache-Control: no-cache, no-store' \
|
||||
-H 'Pragma: no-cache' -L "${FIRST_URL[@]}" -o "$dest" 2>&1 \
|
||||
| tr '\r' '\n' | sed -ur 's|[# ]+||g;s|100||g;s|0-||g;s|.*=.*||g' >> "$PW_TMPFS_PATH/process.log"
|
||||
| tr '\r' '\n' | sed -ur 's|[# ]+||g;s|100||g;s|0-||g;s|.*=.*||g'
|
||||
|
||||
#TODO: check download errror
|
||||
# [[ "${PIPESTATUS[0]}" != 0 ]] && local curl_status="error"
|
||||
@@ -512,47 +545,43 @@ var_ld_library_path_update () {
|
||||
|
||||
# GUI NOTIFY SEND
|
||||
pw_notify_send () {
|
||||
if command -v gdbus &>/dev/null ; then
|
||||
local icon title body app timeout OPTIND
|
||||
app="PortProton"
|
||||
timeout="5000"
|
||||
OPTIND="1"
|
||||
while getopts a:i:t: opt ; do
|
||||
case "$opt" in
|
||||
a)
|
||||
app=$OPTARG
|
||||
;;
|
||||
i)
|
||||
case "$OPTARG" in
|
||||
info) local OPTARG="${PORT_IMG_PATH}/notify/info.svg";;
|
||||
warning) local OPTARG="${PORT_IMG_PATH}/notify/warning.svg";;
|
||||
error) local OPTARG="${PORT_IMG_PATH}/notify/error.svg";;
|
||||
esac
|
||||
icon=$OPTARG
|
||||
;;
|
||||
t)
|
||||
timeout=$OPTARG
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0
|
||||
[-a] name application (PortProton default)
|
||||
[-i] info, warning, error (none default)
|
||||
[-t] timeout in ms (5000 default)" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND - 1 ))
|
||||
[[ "$1" == "--" ]] && shift
|
||||
title="$1" ; shift
|
||||
body="$*"
|
||||
local icon title body app timeout OPTIND
|
||||
app="PortProtonQt"
|
||||
timeout="5000"
|
||||
OPTIND="1"
|
||||
while getopts a:i:t: opt ; do
|
||||
case "$opt" in
|
||||
a)
|
||||
app=$OPTARG
|
||||
;;
|
||||
i)
|
||||
case "$OPTARG" in
|
||||
info) local OPTARG="${PORT_IMG_PATH}/notify/info.svg";;
|
||||
warning) local OPTARG="${PORT_IMG_PATH}/notify/warning.svg";;
|
||||
error) local OPTARG="${PORT_IMG_PATH}/notify/error.svg";;
|
||||
esac
|
||||
icon=$OPTARG
|
||||
;;
|
||||
t)
|
||||
timeout=$OPTARG
|
||||
;;
|
||||
*)
|
||||
echo "usage: $0
|
||||
[-a] name application (PortProtonQt default)
|
||||
[-i] info, warning, error (none default)
|
||||
[-t] timeout in ms (5000 default)" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $(( OPTIND - 1 ))
|
||||
[[ "$1" == "--" ]] && shift
|
||||
title="$1" ; shift
|
||||
body="$*"
|
||||
|
||||
gdbus call --session --dest org.freedesktop.Notifications \
|
||||
--object-path /org/freedesktop/Notifications \
|
||||
--method org.freedesktop.Notifications.Notify \
|
||||
"$app" 0 "$icon" "$title" "$body" "[]" "{}" "$timeout" &>/dev/null
|
||||
return 0
|
||||
fi
|
||||
python_module dbus_tools notify \
|
||||
-a "$app" -i "$icon" -t "$timeout" -- "$title" "$body" &>/dev/null
|
||||
return 0
|
||||
}
|
||||
export -f pw_notify_send
|
||||
|
||||
@@ -671,7 +700,7 @@ unpack () {
|
||||
$PW_TERM "echo ; echo ; echo \"Unpacking file: $1. Please wait...\" ; $command \"$1\" -C \"$2\""
|
||||
[[ "$?" != 0 ]] && return 1 || return 0
|
||||
elif [[ "$silent" != "true" ]] ; then
|
||||
process_log "Unpacking file: $1. Please wait..."
|
||||
print_info "Unpacking file: $(basename "$1") Please wait..."
|
||||
set -o pipefail
|
||||
$command "$1" -C "$2" 2>/dev/null
|
||||
[[ "${PIPESTATUS[0]}" != 0 ]] && print_error "File $1 unpacking error." && return 1 || return 0
|
||||
@@ -1195,7 +1224,7 @@ init_wine_ver () {
|
||||
if [[ -d "${PORT_DATA_PATH}/data/dist" ]] ; then
|
||||
IFS=$'\n'
|
||||
for dist_dir in $(lsbash "${PORT_DATA_PATH}/data/dist") ; do
|
||||
dist_dir_new=$(echo "${dist_dir}" | awk '$1=$1' | sed -e s/[[:blank:]]/_/g)
|
||||
dist_dir_new=$(echo "${dist_dir}" | awk '$1=$1' | sed -e 's/[[:blank:]]*-[[:blank:]]*/-/g' -e 's/[[:blank:]]/_/g')
|
||||
if [[ ! -d "${PORT_DATA_PATH}/data/dist/${dist_dir_new^^}" ]] ; then
|
||||
mv -- "${PORT_DATA_PATH}/data/dist/$dist_dir" "${PORT_DATA_PATH}/data/dist/${dist_dir_new^^}"
|
||||
fi
|
||||
@@ -1294,7 +1323,7 @@ pw_init_runtime () {
|
||||
export PW_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${PW_PLUGINS_PATH}/portable/lib/lib64:${PW_PLUGINS_PATH}/portable/lib/lib32"
|
||||
export PW_VK_LAYER_PATH="${PW_PLUGINS_PATH}/portable/share/vulkan/implicit_layer.d"
|
||||
if [[ -z "$PW_TERM" ]] ; then
|
||||
export PW_TERM="env LD_LIBRARY_PATH=\"${PW_LD_LIBRARY_PATH}\" python3 -m portprotonqt.easyterm -e"
|
||||
export PW_TERM="env LD_LIBRARY_PATH=\"${PW_LD_LIBRARY_PATH}\" python3 -m portprotonqt.scripts_utils.easyterm -e"
|
||||
fi
|
||||
export pw_runtime="env"
|
||||
return 0
|
||||
@@ -1337,6 +1366,10 @@ ${PW_PV_OVERRIDES}/i386-linux-gnu/aliases:\
|
||||
|
||||
export PW_PATH="${PW_PLUGINS_PATH}/portable/bin${PATH:+:$PATH}"
|
||||
|
||||
if [[ "$PW_LOG" == "1" ]] ; then
|
||||
PW_ADD_TO_ARGS_IN_RUNTIME="--verbose $PW_ADD_TO_ARGS_IN_RUNTIME"
|
||||
fi
|
||||
|
||||
if [[ -n "${PW_SANDBOX_HOME_PATH}" && -d "${PW_SANDBOX_HOME_PATH}" ]] ; then
|
||||
export pw_runtime="${PW_WINELIB}/pressure-vessel/bin/pressure-vessel-unruntime \
|
||||
--unshare-home \
|
||||
@@ -1381,7 +1414,7 @@ ${PW_PV_OVERRIDES}/i386-linux-gnu/aliases:\
|
||||
for PWRTMRW in "${PW_RT_MOUNT_RW[@]}" ; do
|
||||
export PRESSURE_VESSEL_FILESYSTEMS_RW="${PRESSURE_VESSEL_FILESYSTEMS_RW:+$PRESSURE_VESSEL_FILESYSTEMS_RW:}${PWRTMRW}"
|
||||
done
|
||||
export PW_TERM="env LD_LIBRARY_PATH=\"${PW_LD_LIBRARY_PATH}\" python3 -m portprotonqt.easyterm -e"
|
||||
export PW_TERM="env LD_LIBRARY_PATH=\"${PW_LD_LIBRARY_PATH}\" python3 -m portprotonqt.scripts_utils.easyterm -e"
|
||||
|
||||
print_info "RUNTIME is enabled"
|
||||
}
|
||||
@@ -1623,20 +1656,16 @@ stop_portproton () {
|
||||
elif [[ "${DESKTOP_SESSION}" =~ "cinnamon" ]] ; then
|
||||
gsettings set org.cinnamon.muffin unredirect-fullscreen-windows false
|
||||
elif [[ "${DESKTOP_SESSION}" =~ "deepin" ]] ; then
|
||||
dbus-send --session --dest=com.deepin.WMSwitcher --type=method_call /com/deepin/WMSwitcher com.deepin.WMSwitcher.RequestSwitchWM
|
||||
python_module dbus_tools deepin-switch-wm &>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
kill_portwine
|
||||
try_remove_dir "${PW_WINELIB}/var"
|
||||
[[ -n $PW_WINELIB ]] && try_remove_dir "${PW_WINELIB}/var"
|
||||
find "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/drive_c/" -maxdepth 1 -type f -name "*.tmp" -delete
|
||||
pw_auto_create_shortcut
|
||||
add_in_stop_portproton
|
||||
if [[ -n "$PW_POWERPROFILECTL_PROFILE" ]] \
|
||||
&& [[ "$(python3 -m portprotonqt.scripts_utils.power_profiles get)" != "$PW_POWERPROFILECTL_PROFILE" ]]
|
||||
then
|
||||
python3 -m portprotonqt.scripts_utils.power_profiles set "$PW_POWERPROFILECTL_PROFILE" &>/dev/null
|
||||
fi
|
||||
pw_restore_power_profile
|
||||
|
||||
if [[ $PW_LOG != 1 ]] && [[ -n $START_PW_TIME_IN_GAME ]] ; then
|
||||
debug_timer --end -s "PW_TIME_IN_GAME"
|
||||
@@ -1906,8 +1935,13 @@ pw_init_db () {
|
||||
fi
|
||||
else
|
||||
IFS=$'\n'
|
||||
#TODO: msi
|
||||
PW_FIND_DB_FILE=$(grep -ilw "#${PORTWINE_DB}.exe" "${PORT_SCRIPTS_PATH}/portwine_db"/* | sed s/".exe"/""/gi)
|
||||
if [[ "${PORTWINE_DB,,}" =~ (setup|install|.msi$) ]] ; then
|
||||
print_info "Force used setup.ppdb files!"
|
||||
PW_FIND_DB_FILE="${PORT_SCRIPTS_PATH}/portwine_db/Setup.ppdb"
|
||||
else
|
||||
PW_FIND_DB_FILE=$(grep -ilw "#${PORTWINE_DB}.exe" "${PORT_SCRIPTS_PATH}/portwine_db"/* | sed s/".exe"/""/gi)
|
||||
fi
|
||||
|
||||
if [[ $(echo "$PW_FIND_DB_FILE" | wc -l) -gt 1 ]] ; then
|
||||
fatal "More than one ppdb file found!"
|
||||
elif [[ $(echo "$PW_FIND_DB_FILE" | wc -l) -eq 1 ]] ; then
|
||||
@@ -1953,7 +1987,7 @@ pw_init_db () {
|
||||
echo "#Author: ${USER}"
|
||||
echo "#${PORTWINE_DB}.exe"
|
||||
echo "#Rating=1-5"
|
||||
} > "${PW_EXE_FILE}".ppdb
|
||||
} > "${PW_EXE_FILE}".ppdb || fatal "Read only: $(dirname "${PW_EXE_FILE}")"
|
||||
export PORTWINE_DB_FILE="${PW_EXE_FILE}".ppdb
|
||||
fi
|
||||
if [[ "${PW_EXE_FILE}" =~ "/prefixes/" ]] && \
|
||||
@@ -1968,11 +2002,6 @@ pw_init_db () {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${PATH_TO_GAME}" ]] || [[ ! -d "${PATH_TO_GAME}" ]] ; then
|
||||
PATH_TO_GAME="$( cd "$( dirname "${PW_EXE_FILE}" )" >/dev/null 2>&1 && pwd )"
|
||||
export PATH_TO_GAME
|
||||
fi
|
||||
|
||||
if [[ "${PW_WINE_CPU_TOPOLOGY}" == "disabled" ]] && [[ -n "${WINE_CPU_TOPOLOGY}" ]] ; then
|
||||
export PW_WINE_CPU_TOPOLOGY="${WINE_CPU_TOPOLOGY}"
|
||||
fi
|
||||
@@ -1989,6 +2018,13 @@ pw_init_db () {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "${PATH_TO_GAME}" ]] || [[ ! -d "$(realpath "${PATH_TO_GAME}")" ]] ; then
|
||||
if [[ -f "${PW_EXE_FILE}" ]] ; then
|
||||
PATH_TO_GAME="$(dirname "$(realpath "${PW_EXE_FILE}")")"
|
||||
export PATH_TO_GAME
|
||||
fi
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -2702,6 +2738,7 @@ start_portproton () {
|
||||
pw_wineboot -r
|
||||
echo "based_on_default" >> "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/winetricks.log"
|
||||
fi
|
||||
get_and_set_reg_file --add 'Control Panel\Desktop' 'LogPixels' 'REG_DWORD' "96" "user"
|
||||
else
|
||||
check_dirs_and_files_in_pfx
|
||||
if [[ ! -f "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/.wine_ver" ]] \
|
||||
@@ -2904,11 +2941,12 @@ start_portproton () {
|
||||
export GAMEMODERUN=0
|
||||
export PW_GAMEMODERUN_SLR=""
|
||||
local current_power_profile
|
||||
current_power_profile=$(python3 -m portprotonqt.scripts_utils.power_profiles get)
|
||||
current_power_profile=$(python_module power_profiles get)
|
||||
if [[ -n "$current_power_profile" && "$current_power_profile" != "performance" ]] \
|
||||
&& python3 -m portprotonqt.scripts_utils.power_profiles set performance &>/dev/null
|
||||
&& python_module power_profiles set performance &>/dev/null
|
||||
then
|
||||
export PW_POWERPROFILECTL_PROFILE="$current_power_profile"
|
||||
pw_save_power_profile "$current_power_profile"
|
||||
print_info "Gamemode replaced by power-profiles-daemon/tuned-ppd/tlp-pd via D-Bus to avoid conflicts"
|
||||
else
|
||||
export PW_POWERPROFILECTL_PROFILE=""
|
||||
@@ -3662,7 +3700,7 @@ start_portproton () {
|
||||
elif [[ "${DESKTOP_SESSION}" =~ "cinnamon" ]] ; then
|
||||
gsettings set org.cinnamon.muffin unredirect-fullscreen-windows true
|
||||
elif [[ "${DESKTOP_SESSION}" =~ "deepin" ]] ; then
|
||||
dbus-send --session --dest=com.deepin.WMSwitcher --type=method_call /com/deepin/WMSwitcher com.deepin.WMSwitcher.RequestSwitchWM
|
||||
python_module dbus_tools deepin-switch-wm &>/dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -3689,14 +3727,14 @@ pw_run () {
|
||||
if [[ -n "${PATH_TO_GAME}" ]] \
|
||||
&& [[ -d "${PATH_TO_GAME}" ]]
|
||||
then
|
||||
cd "${PATH_TO_GAME}" || :
|
||||
:
|
||||
elif [[ -f "$PW_EXE_FILE" ]]
|
||||
then
|
||||
PATH_TO_GAME="$( cd "$( dirname "${PW_EXE_FILE}" )" >/dev/null 2>&1 && pwd )"
|
||||
cd "${PATH_TO_GAME}" || :
|
||||
else
|
||||
cd "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/drive_c" || :
|
||||
PATH_TO_GAME="${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/drive_c" || :
|
||||
fi
|
||||
cd "${PATH_TO_GAME}" || fatal "${PATH_TO_GAME} not found"
|
||||
|
||||
if file "$PW_EXE_FILE" | grep -q "x86-64" ; then
|
||||
check_variables WINE_LARGE_ADDRESS_AWARE "0"
|
||||
@@ -3715,6 +3753,8 @@ pw_run () {
|
||||
if [[ "$PW_USE_RUNTIME" == 1 ]] \
|
||||
&& [[ "$PW_WINE_USE" != "USE_SYSTEM_WINE" ]]
|
||||
then
|
||||
unset PORT_SCRIPTS_PATH
|
||||
export OLDPWD="$(realpath "$PATH_TO_GAME")"
|
||||
if [[ "${PW_LOG}" == 1 ]] ; then
|
||||
echo "export PW_BASE_PFX=$PW_BASE_PFX" >> "$PW_LOG_FILE"
|
||||
echo "WINEDLLOVERRIDES=${WINEDLLOVERRIDES}" >> "${PW_LOG_FILE}"
|
||||
@@ -4584,7 +4624,10 @@ pw_auto_create_shortcut () {
|
||||
|
||||
unset LINKS
|
||||
print_info "checking *.lnk files"
|
||||
for link_file in "${PORT_DATA_PATH}"/data/prefixes/*/drive_c/users/${USER}/Desktop/*.lnk ; do
|
||||
for link_file in "${PORT_DATA_PATH}"/data/prefixes/*/drive_c/users/${USER}/Desktop/*.lnk \
|
||||
"${PORT_DATA_PATH}"/data/prefixes/*/drive_c/users/Public/Desktop/*.lnk
|
||||
do
|
||||
[[ ! -f $link_file ]] && continue
|
||||
link_file=$(readlink -f "$link_file")
|
||||
LINKS+=("${link_file// /#@_@#}")
|
||||
done
|
||||
@@ -4647,65 +4690,25 @@ pw_create_prefix_backup_cli () {
|
||||
PW_PREFIX_NAME="$1"
|
||||
PW_PREFIX_TO_BACKUP="$2"
|
||||
|
||||
if [[ -n "$(grep "/${PW_PREFIX_NAME}/" "${PORT_DATA_PATH}"/*.desktop )" ]] ; then
|
||||
try_remove_file "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/.create_shortcut"
|
||||
grep "/${PW_PREFIX_NAME}/" "${PORT_DATA_PATH}"/*.desktop | awk -F"/${PW_PREFIX_NAME}/" '{print $2}' \
|
||||
| awk -F\" '{print $1}' > "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/.create_shortcut"
|
||||
fi
|
||||
|
||||
unset PW_SANDBOX_HOME_PATH
|
||||
pw_init_runtime
|
||||
if check_flatpak; then
|
||||
TMP_ALL_PATH=""
|
||||
else
|
||||
TMP_ALL_PATH="LD_LIBRARY_PATH=\"${PW_LD_LIBRARY_PATH}\""
|
||||
fi
|
||||
|
||||
chmod -R u+w "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}"
|
||||
|
||||
echo "#!/usr/bin/env bash
|
||||
${TMP_ALL_PATH} \"${PW_PLUGINS_PATH}/portable/bin/mksquashfs\" \"${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}\" \
|
||||
\"${PW_PREFIX_TO_BACKUP}/${PW_PREFIX_NAME}.ppack.part\" -comp zstd \
|
||||
|| rm -f \"${PW_PREFIX_TO_BACKUP}/${PW_PREFIX_NAME}.ppack.part\"
|
||||
" > "${PORT_WINE_TMP_PATH}/pp_pfx_backup.sh"
|
||||
|
||||
chmod u+x "${PORT_WINE_TMP_PATH}/pp_pfx_backup.sh"
|
||||
|
||||
${pw_runtime} ${PW_TERM} "${PORT_WINE_TMP_PATH}/pp_pfx_backup.sh"
|
||||
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_backup.sh"
|
||||
|
||||
if [[ -f "${PW_PREFIX_TO_BACKUP}/${PW_PREFIX_NAME}.ppack.part" ]]; then
|
||||
mv -f "${PW_PREFIX_TO_BACKUP}/${PW_PREFIX_NAME}.ppack.part" \
|
||||
"${PW_PREFIX_TO_BACKUP}/${PW_PREFIX_NAME}.ppack"
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
python3 -m portprotonqt.scripts_utils.prefix_backup --port-data-path "${PORT_DATA_PATH}" \
|
||||
backup "${PW_PREFIX_NAME}" "${PW_PREFIX_TO_BACKUP}"
|
||||
return $?
|
||||
}
|
||||
|
||||
pw_unpack_prefix () {
|
||||
PW_BACKUP_FILE="$1"
|
||||
pw_is_legacy_prefix_backup () {
|
||||
[[ "$(head -c 4 "$1" 2>/dev/null)" == "hsqs" ]]
|
||||
}
|
||||
|
||||
pw_unpack_legacy_prefix () {
|
||||
PW_PREFIX_NAME=$(basename "${PW_BACKUP_FILE}")
|
||||
PW_PREFIX_NAME="${PW_PREFIX_NAME%.[Pp][Pp][Aa][Cc][Kk]}"
|
||||
|
||||
unset PW_SANDBOX_HOME_PATH
|
||||
pw_init_runtime
|
||||
|
||||
if check_flatpak; then
|
||||
TMP_ALL_PATH=""
|
||||
else
|
||||
TMP_ALL_PATH="LD_LIBRARY_PATH=\"${PW_LD_LIBRARY_PATH}\""
|
||||
fi
|
||||
|
||||
if check_selinux; then
|
||||
NO_XATTRS_NEED="-no-xattrs"
|
||||
else
|
||||
NO_XATTRS_NEED=""
|
||||
fi
|
||||
|
||||
PW_PREFIX_NAME=$(basename "${PW_BACKUP_FILE^^}" .PPACK)
|
||||
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_unpack_error"
|
||||
echo "#!/usr/bin/env bash
|
||||
${TMP_ALL_PATH} \"${PW_PLUGINS_PATH}/portable/bin/unsquashfs\" $NO_XATTRS_NEED -f -d \
|
||||
unsquashfs -no-xattrs -f -d \
|
||||
\"${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}\" \"$PW_BACKUP_FILE\" \
|
||||
|| echo \"ERROR\" > \"${PORT_WINE_TMP_PATH}/pp_pfx_unpack_error\"
|
||||
" > "${PORT_WINE_TMP_PATH}/pp_pfx_unpack.sh"
|
||||
@@ -4717,15 +4720,31 @@ pw_unpack_prefix () {
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_unpack_error"
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_unpack.sh"
|
||||
return 1
|
||||
else
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_unpack.sh"
|
||||
if [[ -f "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/.create_shortcut" ]] ; then
|
||||
while read -r line
|
||||
do
|
||||
export PW_EXE_FILE="$PORT_DATA_PATH/data/prefixes/$PW_PREFIX_NAME/$line"
|
||||
portwine_output_yad_shortcut
|
||||
done < "$PORT_DATA_PATH/data/prefixes/$PW_PREFIX_NAME/.create_shortcut"
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_unpack_error"
|
||||
try_remove_file "${PORT_WINE_TMP_PATH}/pp_pfx_unpack.sh"
|
||||
if [[ -f "${PORT_DATA_PATH}/data/prefixes/${PW_PREFIX_NAME}/.create_shortcut" ]] ; then
|
||||
while read -r line
|
||||
do
|
||||
export PW_EXE_FILE="$PORT_DATA_PATH/data/prefixes/$PW_PREFIX_NAME/$line"
|
||||
portwine_output_yad_shortcut
|
||||
done < "$PORT_DATA_PATH/data/prefixes/$PW_PREFIX_NAME/.create_shortcut"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
pw_unpack_prefix () {
|
||||
PW_BACKUP_FILE="$1"
|
||||
|
||||
PW_PREFIX_NAME=$(basename "${PW_BACKUP_FILE}")
|
||||
PW_PREFIX_NAME="${PW_PREFIX_NAME%.[Pp][Pp][Aa][Cc][Kk]}"
|
||||
|
||||
if pw_is_legacy_prefix_backup "${PW_BACKUP_FILE}" ; then
|
||||
pw_unpack_legacy_prefix
|
||||
return $?
|
||||
fi
|
||||
|
||||
python3 -m portprotonqt.scripts_utils.prefix_backup --port-data-path "${PORT_DATA_PATH}" \
|
||||
restore "${PW_BACKUP_FILE}"
|
||||
return $?
|
||||
}
|
||||
@@ -128,7 +128,7 @@ create_new_dir "${HOME}/.local/share/applications"
|
||||
create_new_dir "${PORT_DATA_PATH}/data/dist"
|
||||
IFS=$'\n'
|
||||
for dist_dir in $(lsbash "${PORT_DATA_PATH}/data/dist/") ; do
|
||||
dist_dir_new=$(echo "${dist_dir}" | awk '$1=$1' | sed -e s/[[:blank:]]/_/g)
|
||||
dist_dir_new=$(echo "${dist_dir}" | awk '$1=$1' | sed -e 's/[[:blank:]]*-[[:blank:]]*/-/g' -e 's/[[:blank:]]/_/g')
|
||||
if [[ ! -d "${PORT_DATA_PATH}/data/dist/${dist_dir_new^^}" ]] ; then
|
||||
mv -- "${PORT_DATA_PATH}/data/dist/$dist_dir" "${PORT_DATA_PATH}/data/dist/${dist_dir_new^^}"
|
||||
fi
|
||||
@@ -205,8 +205,8 @@ fi
|
||||
print_info "The first mirror in used: $MIRROR"
|
||||
|
||||
if check_gamescope_session
|
||||
then PW_TERM="env LANG=C python3 -m portprotonqt.easyterm --fullscreen -e"
|
||||
else PW_TERM="env LANG=C python3 -m portprotonqt.easyterm -e"
|
||||
then PW_TERM="env LANG=C python3 -m portprotonqt.scripts_utils.easyterm --fullscreen -e"
|
||||
else PW_TERM="env LANG=C python3 -m portprotonqt.scripts_utils.easyterm -e"
|
||||
fi
|
||||
|
||||
pw_cleanup () {
|
||||
@@ -403,7 +403,12 @@ Usage examples:
|
||||
if [[ -z "${all_vars[$key]+x}" ]]; then
|
||||
all_vars["$key"]="$val"
|
||||
fi
|
||||
done < <(grep -E '^export ' "$PORT_SCRIPTS_PATH/var" | sed -E 's/[[:space:]]*#.*$//' | sed '/^[[:space:]]*$/d')
|
||||
done < <(
|
||||
{
|
||||
grep -E '^export ' "$PORT_SCRIPTS_PATH/var"
|
||||
grep -E '^check_variables ' "$PORT_SCRIPTS_PATH/var" | sed -E 's/^check_variables ([^[:space:]]+) (.*)$/export \1=\2/'
|
||||
} | sed -E 's/[[:space:]]*#.*$//' | sed '/^[[:space:]]*$/d'
|
||||
)
|
||||
|
||||
for key in "${!all_vars[@]}"; do
|
||||
echo "${key}=\"${all_vars[$key]}\""
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
"compatibilitytools"
|
||||
{
|
||||
"compat_tools"
|
||||
{
|
||||
"PortProtonQt"
|
||||
{
|
||||
"install_path" "."
|
||||
"display_name" "PortProtonQt"
|
||||
"from_oslist" "windows"
|
||||
"to_oslist" "linux"
|
||||
"Priority" "75"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
# Install PortProtonQt Steam compatibility tool
|
||||
install_data(
|
||||
'compatibilitytool.vdf',
|
||||
'toolmanifest.vdf',
|
||||
install_dir: prefix / get_option('datadir') / 'steam' / 'compatibilitytools.d' / 'PortProtonQt',
|
||||
)
|
||||
|
||||
install_data(
|
||||
'portproton',
|
||||
install_dir: prefix / get_option('datadir') / 'steam' / 'compatibilitytools.d' / 'PortProtonQt',
|
||||
install_mode: 'rwxr-xr-x',
|
||||
)
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Remove Steam Runtime libraries
|
||||
unset LD_LIBRARY_PATH
|
||||
|
||||
export STEAM_COMPAT=1
|
||||
|
||||
exe_name="$(basename "$1")"
|
||||
exe_dir="$(dirname "$1")"
|
||||
|
||||
# Ignore specific executables
|
||||
if [[ "$exe_name" == "iscriptevaluator.exe" ]] || \
|
||||
[[ "$exe_name" == "d3ddriverquery64.exe" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Copy PPDB file from steam_scripts to exe directory if it exists
|
||||
# Steam passes the appid via SteamAppId environment variable
|
||||
if [[ -n "$SteamAppId" ]]; then
|
||||
# Get PortProton directory from config file or Flatpak defaults
|
||||
portproton_dir=""
|
||||
new_config_file="$HOME/.config/PortProtonQt.conf"
|
||||
legacy_config_file="$HOME/.config/PortProton.conf"
|
||||
new_flatpak_id="ru.linux_gaming.PortProtonQt"
|
||||
legacy_flatpak_id="ru.linux_gaming.PortProton"
|
||||
new_flatpak_config_file="$HOME/.var/app/$new_flatpak_id/config/PortProtonQt.conf"
|
||||
new_flatpak_dir="$HOME/.var/app/$new_flatpak_id/data"
|
||||
legacy_flatpak_dir="$HOME/.var/app/$legacy_flatpak_id/data"
|
||||
|
||||
if [[ -f "$new_config_file" ]]; then
|
||||
portproton_dir="$(awk -F'= ' '/^portdata_path/ {print $2; exit}' "$new_config_file" | tr -d '\n')"
|
||||
fi
|
||||
|
||||
if [[ -z "$portproton_dir" ]] || [[ ! -d "$portproton_dir" ]]; then
|
||||
if [[ -f "$legacy_config_file" ]]; then
|
||||
portproton_dir="$(cat "$legacy_config_file" | tr -d '\n')"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$portproton_dir" ]] || [[ ! -d "$portproton_dir" ]]; then
|
||||
if [[ -f "$new_flatpak_config_file" ]]; then
|
||||
portproton_dir="$(awk -F'= ' '/^portdata_path/ {print $2; exit}' "$new_flatpak_config_file" | tr -d '\n')"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -z "$portproton_dir" ]] || [[ ! -d "$portproton_dir" ]]; then
|
||||
if [[ -d "$new_flatpak_dir" ]]; then
|
||||
portproton_dir="$new_flatpak_dir"
|
||||
elif [[ -d "$legacy_flatpak_dir" ]]; then
|
||||
portproton_dir="$legacy_flatpak_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
ppdb_source="$portproton_dir/steam_scripts/${SteamAppId}.exe.ppdb"
|
||||
ppdb_dest="$exe_dir/${exe_name}.ppdb"
|
||||
|
||||
if [[ -f "$ppdb_source" ]]; then
|
||||
cp "$ppdb_source" "$ppdb_dest"
|
||||
echo "Copied PPDB from $ppdb_source to $ppdb_dest"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Activate virtual environment if available
|
||||
if [[ -n "$PPQT_VENV_PATH" ]]; then
|
||||
cd "$PPQT_VENV_PATH" || exit 1
|
||||
source .venv/bin/activate || exit 1
|
||||
fi
|
||||
|
||||
# Use AppImage if specified, then Flatpak if installed, otherwise use portprotonqt from PATH
|
||||
if [[ -n "$PPQT_BIN_PATH" ]]; then
|
||||
"$PPQT_BIN_PATH" --silent --debug-level INFO "$@"
|
||||
elif command -v flatpak >/dev/null 2>&1 && flatpak info ru.linux_gaming.PortProtonQt >/dev/null 2>&1; then
|
||||
flatpak run ru.linux_gaming.PortProtonQt --silent --debug-level INFO "$@"
|
||||
else
|
||||
portprotonqt --silent --debug-level INFO "$@"
|
||||
fi
|
||||
@@ -1,4 +0,0 @@
|
||||
"manifest"
|
||||
{
|
||||
"commandline" "/portproton"
|
||||
}
|
||||
@@ -654,13 +654,14 @@ class MesonLinter:
|
||||
def get_python_files_from_directory(directory):
|
||||
"""Get all Python files from a directory (excluding __pycache__ and other non-source files)"""
|
||||
python_files = []
|
||||
for file_path in Path(directory).iterdir():
|
||||
base_dir = Path(directory)
|
||||
for file_path in base_dir.rglob('*.py'):
|
||||
if (file_path.is_file() and
|
||||
file_path.suffix == '.py' and
|
||||
file_path.name != '__pycache__' and
|
||||
'__pycache__' not in file_path.parts and
|
||||
not file_path.name.startswith('.') and # Skip hidden files
|
||||
file_path.name != 'meson.build'): # Exclude meson.build itself
|
||||
python_files.append(file_path.name)
|
||||
python_files.append(file_path.relative_to(base_dir).as_posix())
|
||||
return sorted(python_files)
|
||||
|
||||
|
||||
@@ -747,8 +748,28 @@ def check_files(repo_root: Path) -> list[str]:
|
||||
|
||||
dir_files = get_python_files_from_directory(portprotonqt_dir)
|
||||
meson_files = get_files_from_meson_build(portprotonqt_meson)
|
||||
meson_subdirs = get_subdirs_from_meson_build(portprotonqt_meson)
|
||||
|
||||
return [f for f in dir_files if f not in meson_files]
|
||||
return [
|
||||
f for f in dir_files
|
||||
if f not in meson_files and
|
||||
not any(f == subdir or f.startswith(f"{subdir}/") for subdir in meson_subdirs)
|
||||
]
|
||||
|
||||
|
||||
def check_stale_files(repo_root: Path) -> list[str]:
|
||||
"""Check if meson.build lists Python files that no longer exist."""
|
||||
portprotonqt_dir = repo_root / 'portprotonqt'
|
||||
portprotonqt_meson = portprotonqt_dir / 'meson.build'
|
||||
|
||||
if not portprotonqt_meson.exists():
|
||||
return []
|
||||
|
||||
meson_files = get_files_from_meson_build(portprotonqt_meson)
|
||||
return [
|
||||
f for f in meson_files
|
||||
if f.endswith('.py') and not (portprotonqt_dir / f).exists()
|
||||
]
|
||||
|
||||
|
||||
def check_portproton_subdirs(repo_root: Path) -> list[str]:
|
||||
@@ -796,6 +817,7 @@ def main():
|
||||
# Check files inclusion (only if syntax is OK)
|
||||
if exit_code == 0:
|
||||
missing_files = check_files(repo_root)
|
||||
stale_files = check_stale_files(repo_root)
|
||||
missing_subdirs = check_portproton_subdirs(repo_root)
|
||||
|
||||
if missing_files:
|
||||
@@ -805,6 +827,13 @@ def main():
|
||||
print("\nPlease add these files to the install_data section in portprotonqt/meson.build")
|
||||
exit_code = 1
|
||||
|
||||
if stale_files:
|
||||
print("ERROR: The following files are listed in meson.build but do not exist:")
|
||||
for file in stale_files:
|
||||
print(f" - {file}")
|
||||
print("\nPlease remove or update these files in portprotonqt/meson.build")
|
||||
exit_code = 1
|
||||
|
||||
if missing_subdirs:
|
||||
print("ERROR: The following PortProton directories are missing from meson.build:")
|
||||
for directory in missing_subdirs:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
project('portprotonqt', 'c',
|
||||
version: '1.0',
|
||||
version: '1.1.0',
|
||||
meson_version: '>= 0.61.2',
|
||||
license: 'GPL-3.0',
|
||||
)
|
||||
@@ -131,9 +131,6 @@ install_subdir(
|
||||
install_mode: 'rwxr-xr-x',
|
||||
)
|
||||
|
||||
# Install Steam compatibility tool
|
||||
subdir('build-aux/share/steam/compatibilitytools.d/PortProtonQt')
|
||||
|
||||
# Install udev rules
|
||||
udev_rulesdir_opt = get_option('udev_rulesdir')
|
||||
udevdir = udev_rulesdir_opt != '' ? udev_rulesdir_opt : prefix / 'lib' / 'udev' / 'rules.d'
|
||||
|
||||
@@ -3,6 +3,10 @@ import sys
|
||||
import os
|
||||
import importlib
|
||||
import subprocess
|
||||
import threading
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from urllib.parse import quote, unquote
|
||||
from logging import Logger
|
||||
|
||||
__app_id__ = "ru.linux_gaming.PortProtonQt"
|
||||
@@ -14,7 +18,7 @@ try:
|
||||
APP_VERSION = version_module.APP_VERSION
|
||||
except ImportError:
|
||||
APP_COMMIT = ""
|
||||
APP_VERSION = "1.0"
|
||||
APP_VERSION = "1.1.0"
|
||||
|
||||
__app_version__ = APP_VERSION
|
||||
|
||||
@@ -23,11 +27,13 @@ from PySide6.QtWidgets import QApplication, QMenu, QSystemTrayIcon
|
||||
from PySide6.QtGui import QAction, QIcon
|
||||
|
||||
from portprotonqt.config import (
|
||||
consume_download_counter_skip,
|
||||
display_config,
|
||||
get_portproton_start_command,
|
||||
get_portproton_location,
|
||||
save_portdata_path_to_config,
|
||||
ui_config,
|
||||
update_app_version,
|
||||
window_config,
|
||||
)
|
||||
from portprotonqt.logger import get_logger, setup_logger
|
||||
@@ -37,7 +43,6 @@ from portprotonqt.cli import (
|
||||
parse_portproton_url,
|
||||
is_launch_file,
|
||||
is_prefix_backup_file,
|
||||
is_exe_file,
|
||||
normalize_launch_path,
|
||||
add_steam_compat_tool,
|
||||
reinstall_steam_compat_tool,
|
||||
@@ -48,6 +53,8 @@ from portprotonqt.cli import (
|
||||
)
|
||||
from portprotonqt.localization import _, get_steam_language
|
||||
|
||||
COUNTER_DOWNLOAD_URL = "http://cloud.linux-gaming.ru:8081/api/download/{version}"
|
||||
|
||||
def get_version():
|
||||
if APP_COMMIT:
|
||||
return f"{__app_version__} ({APP_COMMIT})"
|
||||
@@ -142,6 +149,22 @@ def restore_prefix_backup(start_sh: list[str], backup_path: str) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def notify_download_counter(app_version: str, logger: Logger) -> None:
|
||||
url = COUNTER_DOWNLOAD_URL.format(version=quote(app_version, safe=""))
|
||||
|
||||
def send_request() -> None:
|
||||
try:
|
||||
request = urllib.request.Request(url, method="GET")
|
||||
with urllib.request.urlopen(request, timeout=10):
|
||||
pass
|
||||
except urllib.error.URLError as e:
|
||||
logger.debug("Failed to notify download counter: %s", e)
|
||||
except OSError as e:
|
||||
logger.debug("Failed to notify download counter: %s", e)
|
||||
|
||||
threading.Thread(target=send_request, daemon=True).start()
|
||||
|
||||
|
||||
def create_prefix_backup(start_sh: list[str], prefix_name: str, backup_dir: str) -> int:
|
||||
"""Create a PortProton prefix backup."""
|
||||
logger = get_logger(__name__)
|
||||
@@ -203,19 +226,9 @@ def main():
|
||||
if start_sh is None:
|
||||
return
|
||||
|
||||
if parsed_args.create_backup:
|
||||
setup_logger(parsed_args.debug_level)
|
||||
prefix_name, backup_dir = parsed_args.create_backup
|
||||
backup_exit_code = create_prefix_backup(start_sh, prefix_name, backup_dir)
|
||||
sys.exit(backup_exit_code)
|
||||
|
||||
if parsed_args.restore_prefix and not parsed_args.file_or_url:
|
||||
setup_logger(parsed_args.debug_level)
|
||||
sys.exit(1)
|
||||
if is_restore_prefix_request(parsed_args):
|
||||
setup_logger(parsed_args.debug_level)
|
||||
restore_exit_code = restore_prefix_backup(start_sh, parsed_args.file_or_url)
|
||||
sys.exit(restore_exit_code)
|
||||
|
||||
# Handle Steam compatibility mode - launch game directly without GUI.
|
||||
if is_steam_compat:
|
||||
@@ -254,9 +267,15 @@ def main():
|
||||
args = parsed_args
|
||||
setup_logger(args.debug_level)
|
||||
logger = get_logger(__name__)
|
||||
if consume_download_counter_skip():
|
||||
update_app_version(__app_version__)
|
||||
elif update_app_version(__app_version__):
|
||||
notify_download_counter(__app_version__, logger)
|
||||
|
||||
fullscreen = args.fullscreen or display_config.get_fullscreen()
|
||||
ipc_message = "show:fullscreen" if fullscreen else "show"
|
||||
backup_request = None
|
||||
restore_prefix_path = None
|
||||
resolution_from_args = None
|
||||
if args.resolution:
|
||||
resolution_from_args = parse_resolution(args.resolution)
|
||||
@@ -266,16 +285,23 @@ def main():
|
||||
window_config.set_geometry(resolution_from_args[0], resolution_from_args[1])
|
||||
logger.info("Saved window resolution: %sx%s", resolution_from_args[0], resolution_from_args[1])
|
||||
ipc_message = "noop"
|
||||
if is_silent_launch and args.file_or_url and is_exe_file(args.file_or_url):
|
||||
if args.create_backup:
|
||||
prefix_name, backup_dir = args.create_backup
|
||||
backup_request = (prefix_name, os.path.abspath(os.path.expanduser(backup_dir)))
|
||||
ipc_message = "backup:{}:{}".format(quote(prefix_name, safe=""), quote(backup_request[1], safe=""))
|
||||
elif is_silent_launch and args.file_or_url and is_launch_file(args.file_or_url):
|
||||
run_silent_tray(app, start_sh, normalize_launch_path(args.file_or_url))
|
||||
sys.exit(app.exec())
|
||||
elif is_restore_prefix_request(args):
|
||||
restore_prefix_path = normalize_launch_path(args.file_or_url)
|
||||
ipc_message = f"restore:{quote(restore_prefix_path, safe='')}"
|
||||
elif args.file_or_url and is_launch_file(args.file_or_url):
|
||||
ipc_message = f"open:{normalize_launch_path(args.file_or_url)}"
|
||||
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from PySide6.QtNetwork import QLocalServer, QLocalSocket
|
||||
from portprotonqt.main_window import MainWindow
|
||||
from portprotonqt.port_data_path_selector import ask_portdata_path
|
||||
from portprotonqt.port_data_path_selector import ask_portdata_path, is_portdata_path_read_write
|
||||
from portprotonqt.portproton_api import (
|
||||
PortProtonAPI,
|
||||
get_user_conf_setting,
|
||||
@@ -309,8 +335,14 @@ def main():
|
||||
logger.warning(f"Failed to start local server: {local_server.errorString()}")
|
||||
return
|
||||
|
||||
portdata_warning = None
|
||||
if portproton_location and not is_portdata_path_read_write(portproton_location):
|
||||
logger.warning("PORT_DATA_PATH is not readable/writable: %s", portproton_location)
|
||||
portdata_warning = _("PortProton data folder is not readable and writable. Choose another folder for PortProton data.")
|
||||
portproton_location = None
|
||||
|
||||
if not portproton_location:
|
||||
portproton_location = ask_portdata_path()
|
||||
portproton_location = ask_portdata_path(portdata_warning, bool(portdata_warning))
|
||||
if not portproton_location:
|
||||
logger.error("PORT_DATA_PATH is not configured, startup aborted")
|
||||
return
|
||||
@@ -319,7 +351,7 @@ def main():
|
||||
logger.warning("Failed to persist PORT_DATA_PATH in PortProtonQt config")
|
||||
|
||||
# Check if we have a portproton:// URL or launch file to handle
|
||||
if args.file_or_url:
|
||||
if args.file_or_url and not restore_prefix_path:
|
||||
if is_portproton_url(args.file_or_url):
|
||||
# Parse the portproton:// URL to get the full download URL
|
||||
download_url = parse_portproton_url(args.file_or_url)
|
||||
@@ -366,6 +398,14 @@ def main():
|
||||
def handle_launch_exe():
|
||||
window.handle_launch_exe(exe_path)
|
||||
QTimer.singleShot(0, handle_launch_exe)
|
||||
elif backup_request:
|
||||
def handle_create_backup():
|
||||
window._perform_backup(backup_request[1], backup_request[0])
|
||||
QTimer.singleShot(0, handle_create_backup)
|
||||
elif restore_prefix_path:
|
||||
def handle_restore_prefix():
|
||||
window._perform_restore(restore_prefix_path)
|
||||
QTimer.singleShot(0, handle_restore_prefix)
|
||||
|
||||
# --- Handle incoming connections ---
|
||||
def handle_new_connection():
|
||||
@@ -380,7 +420,12 @@ def main():
|
||||
|
||||
def restore_window():
|
||||
try:
|
||||
if msg.startswith("show") or msg.startswith("open:"):
|
||||
if (
|
||||
msg.startswith("show")
|
||||
or msg.startswith("open:")
|
||||
or msg.startswith("restore:")
|
||||
or msg.startswith("backup:")
|
||||
):
|
||||
# Ensure the window is visible and not minimized
|
||||
window.setWindowState(window.windowState() & ~Qt.WindowState.WindowMinimized)
|
||||
window.show()
|
||||
@@ -407,6 +452,22 @@ def main():
|
||||
window.handle_launch_exe(launch_path)
|
||||
else:
|
||||
logger.warning("Invalid launch file via IPC: %s", launch_path)
|
||||
elif msg.startswith("restore:"):
|
||||
backup_path = unquote(msg[8:].strip())
|
||||
if backup_path and is_prefix_backup_file(backup_path):
|
||||
logger.info("Restoring prefix backup via IPC: %s", backup_path)
|
||||
window._perform_restore(backup_path)
|
||||
else:
|
||||
logger.warning("Invalid prefix backup via IPC: %s", backup_path)
|
||||
elif msg.startswith("backup:"):
|
||||
parts = msg.split(":", 2)
|
||||
if len(parts) == 3:
|
||||
prefix_name = unquote(parts[1])
|
||||
backup_dir = unquote(parts[2])
|
||||
logger.info("Creating prefix backup via IPC: %s", prefix_name)
|
||||
window._perform_backup(backup_dir, prefix_name)
|
||||
else:
|
||||
logger.warning("Invalid prefix backup request via IPC: %s", msg)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to restore window: {e}")
|
||||
|
||||
@@ -419,12 +480,20 @@ def main():
|
||||
|
||||
# --- Initial fullscreen state ---
|
||||
launch_fullscreen = args.fullscreen or display_config.get_fullscreen()
|
||||
launch_auto_fullscreen = (
|
||||
display_config.get_auto_fullscreen_gamepad()
|
||||
and not launch_fullscreen
|
||||
and getattr(window.input_manager, "gamepad", None) is not None
|
||||
)
|
||||
launch_minimized = (
|
||||
display_config.get_start_minimized()
|
||||
and not args.fullscreen
|
||||
and not launch_auto_fullscreen
|
||||
and window_resolution is None
|
||||
and exe_path is None
|
||||
)
|
||||
and backup_request is None
|
||||
and restore_prefix_path is None
|
||||
)
|
||||
if launch_minimized:
|
||||
logger.info("Launching in tray")
|
||||
window.hide()
|
||||
@@ -434,6 +503,10 @@ def main():
|
||||
)
|
||||
display_config.set_fullscreen(True)
|
||||
window.showFullScreen()
|
||||
elif launch_auto_fullscreen:
|
||||
logger.info("Launching in fullscreen mode (gamepad)")
|
||||
window.input_manager.handle_fullscreen_slot(True)
|
||||
window.updateControlHints("force")
|
||||
elif window_resolution:
|
||||
logger.info(f"Launching with resolution: {window_resolution[0]}x{window_resolution[1]}")
|
||||
window.resize(window_resolution[0], window_resolution[1])
|
||||
|
||||
@@ -6,7 +6,7 @@ from pathlib import Path
|
||||
|
||||
from portprotonqt.steam_api import get_steam_home
|
||||
|
||||
LAUNCH_FILE_EXTENSIONS = ('.exe', '.iso', '.mdf')
|
||||
LAUNCH_FILE_EXTENSIONS = ('.exe', '.bat', '.msi', '.reg', '.iso', '.mdf')
|
||||
PREFIX_BACKUP_EXTENSION = '.ppack'
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ def parse_args():
|
||||
parser.add_argument(
|
||||
"--silent",
|
||||
action="store_true",
|
||||
help="Launch .exe file in tray without showing the main window"
|
||||
help="Launch supported Windows file in tray without showing the main window"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--restore-prefix",
|
||||
@@ -82,7 +82,7 @@ def parse_args():
|
||||
parser.add_argument(
|
||||
'file_or_url',
|
||||
nargs='?',
|
||||
help="Launch file path (.exe/.iso/.mdf) or portproton:// URL"
|
||||
help="Launch file path (.exe/.bat/.msi/.reg/.iso/.mdf) or portproton:// URL"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
@@ -156,13 +156,33 @@ unset LD_LIBRARY_PATH
|
||||
|
||||
export STEAM_COMPAT=1
|
||||
|
||||
|
||||
exe_name="$(basename "$1")"
|
||||
exe_dir="$(dirname "$1")"
|
||||
|
||||
# Ignore specific executables
|
||||
if [[ "$exe_name" == "iscriptevaluator.exe" ]] || \\
|
||||
[[ "$exe_name" == "d3ddriverquery64.exe" ]]; then
|
||||
if [[ "$exe_name" == "iscriptevaluator.exe" ]] \\
|
||||
|| [[ "$exe_name" == "d3ddriverquery64.exe" ]]
|
||||
then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Activate virtual environment if available
|
||||
if [[ -n "$PPQT_VENV_PATH" ]]; then
|
||||
cd "$PPQT_VENV_PATH" || exit 1
|
||||
source .venv/bin/activate || exit 1
|
||||
fi
|
||||
|
||||
if [[ "$UPDATE_CT_PPQT" != "1" ]]; then
|
||||
export UPDATE_CT_PPQT="1"
|
||||
full_command_line=("$(realpath "$0")" "$@")
|
||||
if [[ -n "$PPQT_BIN_PATH" ]]; then
|
||||
"$PPQT_BIN_PATH" --reinstall-steam-compat-tool
|
||||
elif command -v flatpak >/dev/null 2>&1 && flatpak info ru.linux_gaming.PortProtonQt >/dev/null 2>&1; then
|
||||
flatpak run ru.linux_gaming.PortProtonQt --reinstall-steam-compat-tool
|
||||
else
|
||||
portprotonqt --reinstall-steam-compat-tool
|
||||
fi
|
||||
exec "${full_command_line[@]}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -212,12 +232,6 @@ if [[ -n "$SteamAppId" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Activate virtual environment if available
|
||||
if [[ -n "$PPQT_VENV_PATH" ]]; then
|
||||
cd "$PPQT_VENV_PATH" || exit 1
|
||||
source .venv/bin/activate || exit 1
|
||||
fi
|
||||
|
||||
# Use AppImage if specified, then Flatpak if installed, otherwise use portprotonqt from PATH
|
||||
if [[ -n "$PPQT_BIN_PATH" ]]; then
|
||||
"$PPQT_BIN_PATH" --debug-level INFO "$@"
|
||||
@@ -290,9 +304,22 @@ def clear_cache() -> bool:
|
||||
|
||||
def reset_settings() -> bool:
|
||||
"""Reset PortProtonQt settings."""
|
||||
from portprotonqt.config import reset_main_config
|
||||
from portprotonqt.config import (
|
||||
get_portproton_location,
|
||||
reset_main_config,
|
||||
)
|
||||
|
||||
portproton_location = get_portproton_location()
|
||||
reset_main_config()
|
||||
if portproton_location:
|
||||
user_conf_path = Path(portproton_location) / "data" / "user.conf"
|
||||
try:
|
||||
if user_conf_path.is_file():
|
||||
user_conf_path.unlink()
|
||||
except OSError as e:
|
||||
print(f"Failed to delete user.conf: {e}")
|
||||
return False
|
||||
|
||||
print("PortProtonQt settings reset")
|
||||
return True
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ from portprotonqt.config.base import (
|
||||
CACHE_DIR,
|
||||
CONFIG_DIR,
|
||||
CONFIG_FILE,
|
||||
COUNTER_SKIP_FILE,
|
||||
PORTPROTON_CONFIG_FILE,
|
||||
THEMES_DIRS,
|
||||
consume_download_counter_skip,
|
||||
reset_main_config,
|
||||
update_app_version,
|
||||
)
|
||||
from portprotonqt.config.cache import CacheConfig, CacheManager
|
||||
from portprotonqt.config.display import DisplayConfig, apply_xdg_autostart
|
||||
@@ -15,6 +18,9 @@ from portprotonqt.config.game import GameConfig
|
||||
from portprotonqt.config.gamepad import GamepadConfig
|
||||
from portprotonqt.config.portproton import (
|
||||
PortProtonConfig,
|
||||
LAUNCH_FILE_EXTENSIONS,
|
||||
THEMED_LAUNCH_ICON_NAMES,
|
||||
WINDOWS_LAUNCH_EXTENSIONS,
|
||||
create_desktop_file,
|
||||
extract_exec_target_path,
|
||||
find_game_by_exe,
|
||||
@@ -55,7 +61,11 @@ __all__ = [
|
||||
"FavoritesConfig",
|
||||
"FavoritesFoldersConfig",
|
||||
"PortProtonConfig",
|
||||
"LAUNCH_FILE_EXTENSIONS",
|
||||
"THEMED_LAUNCH_ICON_NAMES",
|
||||
"WINDOWS_LAUNCH_EXTENSIONS",
|
||||
"CONFIG_FILE",
|
||||
"COUNTER_SKIP_FILE",
|
||||
"PORTPROTON_CONFIG_FILE",
|
||||
"CONFIG_DIR",
|
||||
"THEMES_DIRS",
|
||||
@@ -83,5 +93,7 @@ __all__ = [
|
||||
"extract_exec_target_path",
|
||||
"find_game_by_exe",
|
||||
"create_desktop_file",
|
||||
"consume_download_counter_skip",
|
||||
"reset_main_config",
|
||||
"update_app_version",
|
||||
]
|
||||
@@ -13,6 +13,7 @@ __all__ = ["configparser"]
|
||||
# Configuration file paths
|
||||
CONFIG_DIR = Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config"))
|
||||
CONFIG_FILE = CONFIG_DIR / "PortProtonQt.conf"
|
||||
COUNTER_SKIP_FILE = CONFIG_DIR / "PortProtonQt.counter.skip"
|
||||
PORTPROTON_CONFIG_FILE = CONFIG_DIR / "PortProton.conf"
|
||||
|
||||
# Theme directories
|
||||
@@ -32,6 +33,7 @@ _config_mtime: dict[str, float] = {}
|
||||
|
||||
def reset_main_config() -> None:
|
||||
"""Delete main config file and invalidate cache."""
|
||||
_mark_download_counter_skip()
|
||||
if not CONFIG_FILE.exists():
|
||||
return
|
||||
try:
|
||||
@@ -46,6 +48,60 @@ def reset_main_config() -> None:
|
||||
_config_mtime.pop(config_path, None)
|
||||
|
||||
|
||||
def _mark_download_counter_skip() -> None:
|
||||
"""Skip the next download counter request."""
|
||||
try:
|
||||
COUNTER_SKIP_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
COUNTER_SKIP_FILE.write_text("1", encoding="utf-8")
|
||||
except OSError as error:
|
||||
logger.warning("Failed to save counter skip marker: %s", error)
|
||||
|
||||
|
||||
def consume_download_counter_skip() -> bool:
|
||||
"""Return True once after settings reset."""
|
||||
if not COUNTER_SKIP_FILE.exists():
|
||||
return False
|
||||
try:
|
||||
COUNTER_SKIP_FILE.unlink()
|
||||
except OSError as error:
|
||||
logger.warning("Failed to delete counter skip marker: %s", error)
|
||||
return True
|
||||
|
||||
|
||||
def update_app_version(app_version: str) -> bool:
|
||||
"""Store current app version and report whether it changed."""
|
||||
cp = configparser.ConfigParser()
|
||||
config_existed = CONFIG_FILE.exists()
|
||||
if config_existed:
|
||||
try:
|
||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
||||
except configparser.Error as e:
|
||||
logger.warning("Invalid configuration file format: %s", e)
|
||||
return False
|
||||
|
||||
section = "Application"
|
||||
old_version = cp.get(section, "version", fallback="")
|
||||
if config_existed and old_version == app_version:
|
||||
return False
|
||||
|
||||
if section not in cp:
|
||||
cp[section] = {}
|
||||
cp[section]["version"] = app_version
|
||||
|
||||
try:
|
||||
CONFIG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(CONFIG_FILE, "w", encoding="utf-8") as config_file:
|
||||
cp.write(config_file)
|
||||
except OSError as error:
|
||||
logger.warning("Failed to save application version: %s", error)
|
||||
return False
|
||||
|
||||
config_path = str(CONFIG_FILE)
|
||||
_config_cache.pop(config_path, None)
|
||||
_config_mtime.pop(config_path, None)
|
||||
return True
|
||||
|
||||
|
||||
class BaseConfig:
|
||||
"""Base configuration class providing common functionality."""
|
||||
|
||||
|
||||
@@ -307,6 +307,16 @@ def parse_desktop_entry(file_path: str) -> configparser.SectionProxy | None:
|
||||
return cp["Desktop Entry"]
|
||||
|
||||
|
||||
WINDOWS_LAUNCH_EXTENSIONS = (".exe", ".bat", ".msi", ".reg")
|
||||
DISC_IMAGE_EXTENSIONS = (".iso", ".mdf")
|
||||
LAUNCH_FILE_EXTENSIONS = WINDOWS_LAUNCH_EXTENSIONS + DISC_IMAGE_EXTENSIONS
|
||||
THEMED_LAUNCH_ICON_NAMES = {
|
||||
".bat": "bat",
|
||||
".msi": "msi",
|
||||
".reg": "reg",
|
||||
}
|
||||
|
||||
|
||||
def extract_exec_target_path(exec_value: str | list[str]) -> str | None:
|
||||
"""Extract target executable or image path from a desktop Exec value."""
|
||||
if isinstance(exec_value, str):
|
||||
@@ -327,7 +337,7 @@ def extract_exec_target_path(exec_value: str | list[str]) -> str | None:
|
||||
return None
|
||||
|
||||
for part in reversed(parts):
|
||||
if part.lower().endswith((".exe", ".iso", ".mdf")):
|
||||
if part.lower().endswith(LAUNCH_FILE_EXTENSIONS):
|
||||
return os.path.expanduser(part)
|
||||
|
||||
if parts[0] in ("env", "flatpak"):
|
||||
@@ -388,7 +398,7 @@ def create_desktop_file(
|
||||
else:
|
||||
exec_str = f'portprotonqt --silent "{exe_path}"'
|
||||
|
||||
comment = _('Launch game "{name}" with PortProton').format(name=game_name)
|
||||
comment = _('Launch "{name}" with PortProton').format(name=game_name)
|
||||
desktop_entry = (
|
||||
"[Desktop Entry]\n"
|
||||
f"Name={game_name}\n"
|
||||
|
||||
|
Before Width: | Height: | Size: 499 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Albion Online
|
||||
description_ru=Многопользовательская песочница в жанре MMORPG, где игроки могут исследовать открытый мир, заниматься ремеслом, добычей ресурсов и сражаться с другими игроками. Игра предлагает уникальную систему классов, позволяющую игрокам изменять свои роли в зависимости от выбранного снаряжения.
|
||||
description_en=A multiplayer sandbox MMORPG where players can explore an open world, engage in crafting, gather resources, and battle against other players. The game features a unique class system that allows players to change their roles based on the gear they equip.
|
||||
|
Before Width: | Height: | Size: 147 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Ankama Launcher
|
||||
description_ru=Лаунчер для игр Ankama.
|
||||
description_en=Launcher for Ankama studio games.
|
||||
|
Before Width: | Height: | Size: 720 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Anomaly Zone
|
||||
description_ru=Экшен-игра про сталкеров, где игроки исследуют таинственные миры и сражаются с разнообразными противниками. Игра предлагает захватывающий сюжет и возможность улучшать персонажа, открывая новые способности и снаряжение.
|
||||
description_en=An action game about stalkers, where players explore mysterious worlds and fight against a variety of opponents. The game offers an exciting storyline and the opportunity to improve the character by unlocking new abilities and equipment.
|
||||
|
Before Width: | Height: | Size: 352 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Arizona Games Launcher
|
||||
description_ru=Лаунчер для игры Arizona Role Play.
|
||||
description_en=Launcher for the Arizona Role Play game.
|
||||
|
Before Width: | Height: | Size: 121 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Battle.net Launcher
|
||||
description_ru=Лаунчер для игр Activision и Blizzard.
|
||||
description_en=Launcher for Activision and Blizzard studio games.
|
||||
|
Before Width: | Height: | Size: 528 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Black Desert Online (RU)
|
||||
description_ru=Многопользовательская ролевая игра с открытым миром, известная своей потрясающей графикой и глубокой системой кастомизации персонажей. Игроки могут исследовать обширные земли, выполнять задания, участвовать в PvP-сражениях и заниматься различными ремеслами.
|
||||
description_en=A massively multiplayer online role-playing game set in an open world, renowned for its stunning graphics and deep character customization system. Players can explore vast lands, complete quests, engage in PvP battles, and participate in various crafting activities.
|
||||
|
Before Width: | Height: | Size: 655 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Blood and Soul
|
||||
description_ru=Многопользовательская ролевая игра с богатой системой боя и яркой графикой, вдохновленная восточной мифологией. Игроки могут выбирать из различных классов, исследовать уникальные локации и сражаться с могущественными врагами.
|
||||
description_en=A multiplayer role-playing game featuring a rich combat system and vibrant graphics, inspired by Eastern mythology. Players can choose from various classes, explore unique locations, and battle powerful foes.
|
||||
|
Before Width: | Height: | Size: 660 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Chicken Invaders Universe
|
||||
description_ru=Захватывающая аркадная игра, в которой игроки сражаются с агрессивными курицами из космоса, защищая свою планету. Игра предлагает множество уровней, кооперативный режим и возможность улучшения космического корабля.
|
||||
description_en=An exciting arcade game where players battle aggressive space chickens to defend their planet. The game features multiple levels, a cooperative mode, and the ability to upgrade their spaceship.
|
||||
|
Before Width: | Height: | Size: 324 KiB |
@@ -1,3 +0,0 @@
|
||||
name=CONTRACT WARS
|
||||
description_ru=Многопользовательский шутер от первого лица, где игроки участвуют в противостоянии между двумя командами на разнообразных картах. Игра предлагает широкий выбор оружия и возможностей для настройки персонажей, что делает каждый матч уникальным.
|
||||
description_en=A multiplayer first-person shooter where players engage in battles between two teams on various maps. The game offers a wide selection of weapons and character customization options, making each match unique.
|
||||
|
Before Width: | Height: | Size: 870 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Age of Empires Online
|
||||
description_ru=Многопользовательская стратегия в реальном времени, где игроки строят свои цивилизации, собирают ресурсы и сражаются с противниками. Игра предлагает уникальную экономическую систему и возможность развивать свои города с помощью различных миссий и задач.
|
||||
description_en=A multiplayer real-time strategy game where players build their civilizations, gather resources, and battle against opponents. The game features a unique economic system and the ability to develop cities through various missions and quests.
|
||||
|
Before Width: | Height: | Size: 276 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Cemu
|
||||
description_ru=Эмулятор Wii U, который позволяет пользователям запускать и играть в игры, выпущенные на этой консоли, с высокой производительностью и улучшенной графикой.
|
||||
description_en=A Wii U emulator that allows users to launch and play games released on this console with high performance and enhanced graphics.
|
||||
|
Before Width: | Height: | Size: 764 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Secret World Legends (ENG)
|
||||
description_ru=MMORPG с уникальной историей и сеттингом, основанная на мифах, легендах и тайных обществах, позволяющая игрокам исследовать современный мир, полный сверхъестественных существ и загадок. Игра предлагает свободу в выборе навыков и построении персонажей, а также захватывающие квесты и глубокий сюжет.
|
||||
description_en=An MMORPG with a unique story and setting based on myths, legends, and secret societies, allowing players to explore a modern world filled with supernatural beings and mysteries. The game offers freedom in skill selection and character building, along with engaging quests and a rich narrative.
|
||||
|
Before Width: | Height: | Size: 395 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Broken Ranks (ENG)
|
||||
description_ru=MMORPG игра, предлагающая глубокий сон и уникальную боевую систему, которая ориентирует внимание на стратегию и деятельность персоны. Игроки исследуют мрачный мир, полный интриг и опасностей, развивая своих героев и принимая ключевые решения, влияющие на ход истории.
|
||||
description_en=An MMORPG game offering deep sleep and a unique combat system that focuses on the strategy and activities of the person. Players explore a dark world full of intrigue and danger, developing their characters and making key decisions that affect the course of history.
|
||||
|
Before Width: | Height: | Size: 36 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Dolphin 5.0
|
||||
description_ru=Мощный эмулятор для игровых консолей Nintendo GameCube и Wii, который позволяет запускать игры на ПК с улучшенной графикой и производительностью. Он поддерживает широкий спектр функций, включая HD-разрешение, множество настройек управления и возможность использования модификаций.
|
||||
description_en=A powerful emulator for Nintendo GameCube and Wii consoles that allows users to play games on their PCs with enhanced graphics and performance. It supports a wide range of features, including HD resolution, numerous control configurations, and the ability to use modifications.
|
||||
|
Before Width: | Height: | Size: 736 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Doomsday
|
||||
description_ru=Стратегическая игра в реальном времени, где игроки управляют отрядом выживших в постапокалиптическом мире, стремясь восстановить цивилизацию и защититься от различных угроз. Игра предлагает элементы строительства базы, тактические сражения и глубокую проработку сюжета.
|
||||
description_en=A real-time strategy game where players manage a group of survivors in a post-apocalyptic world, aiming to rebuild civilization and defend against various threats. The game features base-building elements, tactical combat, and a deep narrative experience.
|
||||
|
Before Width: | Height: | Size: 523 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Eldevin (ENG)
|
||||
description_ru=MMORPG с красочной графикой, где игроки исследуют обширный фэнтезийный мир, выполняют квесты и сражаются с врагами, чтобы развивать свои персонажи. Игра предлагает разнообразные классы и навыки, а также системы крафта и группового взаимодействия.
|
||||
description_en=An MMORPG with vibrant graphics where players explore a vast fantasy world, complete quests, and battle enemies to develop their characters. The game features diverse classes and skills, as well as crafting and group interaction systems.
|
||||
|
Before Width: | Height: | Size: 267 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Epic Games Launcher
|
||||
description_ru=Лаунчер для библиотеки игр Epic Games.
|
||||
description_en=Launcher for the Epic Games game library.
|
||||
|
Before Width: | Height: | Size: 824 KiB |
@@ -1,3 +0,0 @@
|
||||
name=STALCRAFT
|
||||
description_ru=Многопользовательская игра с открытым миром, вдохновленная вселенной S.T.A.L.K.E.R., где игроки исследуют заброшенные зоны, сражаются с мутантами и другими сталкерами, а также выполняют различные квесты. Игра сочетает элементы выживания, RPG и шутера от первого лица, предлагая уникальный опыт в постапокалиптическом мире.
|
||||
description_en=A multiplayer open-world game inspired by the S.T.A.L.K.E.R. universe, where players explore abandoned zones, battle mutants and other stalkers, and complete various quests. The game combines elements of survival, RPG, and first-person shooter, offering a unique experience in a post-apocalyptic world.
|
||||
|
Before Width: | Height: | Size: 634 KiB |
@@ -1,3 +0,0 @@
|
||||
name=ExoTanks
|
||||
description_ru=Многопользовательская боевая игра, в которой игроки управляют экзоскелетами и сражаются в различных аренах, используя мощное вооружение и стратегический подход. Игра предлагает как командные, так и одиночные режимы, а также возможность кастомизации своего экзоскелета для уникального стиля игры.
|
||||
description_en=A multiplayer battle game where players control exoskeletons and fight in various arenas using powerful weapons and strategic gameplay. The game offers both team and solo modes, along with the ability to customize their exoskeleton for a unique playing style.
|
||||
|
Before Width: | Height: | Size: 854 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Farlight 84
|
||||
description_ru=Многопользовательская игра в жанре королевская битва, которая проходит в красочном и футуристическом мире, где игроки сражаются друг с другом с использованием уникальных навыков и оружия. Игра предлагает захватывающий геймплей с элементами строительства, а также возможность использовать различные транспортные средства для перемещения по карте.
|
||||
description_en=A multiplayer battle royale game set in a colorful and futuristic world where players fight against each other using unique skills and weapons. The game features exciting gameplay with building elements, as well as the ability to utilize various vehicles to navigate the map.
|
||||
|
Before Width: | Height: | Size: 860 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Fractured Online (ENG)
|
||||
description_ru=Fractured Online — это первая массовая многопользовательская ролевая онлайн-игра с открытым миром, сочетающая динамичные сражения с полностью интерактивным окружением. Она одинаково понравится любителям соревновательного и кооперативного игрового процесса. С самого первого дня погрузитесь в бой. Побеждайте врагов благодаря собственным навыкам и смекалке, а не снаряжению или уровню. Собирайте ресурсы, создавайте предметы, торгуйте и отправляйтесь в легендарные путешествия в одиночку или создайте поселение со своей гильдией и превратите его в следующую империю.
|
||||
description_en=Fractured Online is the first open-world sandbox MMORPG mixing action combat with fully interactable environments, appealing equally to lovers of competitive and cooperative gameplay. Jump right into the fray from day one. Defeat your enemies through your own skill and cleverness, not equipment or level. Gather resources, craft, trade and venture into legendary travels as a solitary hero, or start a settlement with your guild and grow it into the next empire.
|
||||
|
Before Width: | Height: | Size: 439 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Goose Goose Duck
|
||||
description_ru=Многопользовательская игра в жанре социальной дедукции, где игроки выступают в роли уток или гусей, пытаясь выполнить задания и выявить среди них "уток" — предателей. Игра сочетает в себе элементы стратегии и общения, требуя от игроков координации и способности распознавать обман.
|
||||
description_en=A multiplayer social deduction game where players take on the roles of ducks or geese, trying to complete tasks and identify the "ducks" — the impostors among them. The game combines elements of strategy and communication, requiring players to coordinate and recognize deception.
|
||||
|
Before Width: | Height: | Size: 650 KiB |
@@ -1,3 +0,0 @@
|
||||
name=GOG Galaxy Launcher
|
||||
description_ru=Лаунчер для библиотеки игр GOG.
|
||||
description_en=Launcher for the GOG game library.
|
||||
|
Before Width: | Height: | Size: 283 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Guild Wars 2
|
||||
description_ru=MMORPG с ярким миром и уникальной системой динамических событий, где игроки могут свободно исследовать просторы Тираи и участвовать в масштабных сражениях. Игра предлагает разнообразие рас и классов, а также акцент на совместной игре и взаимодействии между игроками.
|
||||
description_en=An MMORPG with a vibrant world and a unique system of dynamic events, where players can freely explore the realms of Tyria and engage in large-scale battles. The game offers a variety of races and classes, with an emphasis on cooperative play and player interaction.
|
||||
|
Before Width: | Height: | Size: 228 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Indiegala Client
|
||||
description_ru=Лаунчер для библиотеки игр Indiegala.
|
||||
description_en=Launcher for the Indiegala game library.
|
||||
|
Before Width: | Height: | Size: 833 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Last Chaos
|
||||
description_ru=Last Chaos – классическая MMORPG с шестью классами, осадами замков, корейским гриндом и километрами подземелий. Противостояние Апполона и Эреса набирает обороты, так что спешите принять одну из сторон.
|
||||
description_en=Last Chaos is a classic MMORPG with six classes, castle sieges, a Korean grind and kilometers of dungeons. The confrontation between Apollo and Eres is gaining momentum, so hurry up to take one of the sides.
|
||||
|
Before Width: | Height: | Size: 784 KiB |
@@ -1,3 +0,0 @@
|
||||
name=DC Universe Online (ENG)
|
||||
description_ru=MMORPG, в которой игроки создают собственных супергероев или суперзлодеев во вселенной DC Comics и участвуют в эпических битвах с известными персонажами, такими как Супермен и Бэтмен. Игра предлагает обширные квесты, захватывающие PvP-режимы и возможность совместной игры с другими игроками.
|
||||
description_en=An MMORPG where players create their own superheroes or supervillains in the DC Comics universe and engage in epic battles alongside iconic characters like Superman and Batman. The game features extensive quests, exciting PvP modes, and the ability to team up with other players.
|
||||
|
Before Width: | Height: | Size: 653 KiB |
@@ -1,3 +0,0 @@
|
||||
name=The Lord of the Rings Online (ENG)
|
||||
description_ru=MMORPG, основанная на произведениях Дж. Р. Р. Толкиена, позволяющая игрокам исследовать Средиземье, принимать участие в эпических квестах и сражениях с известными персонажами и созданиями из вселенной Властелина колец. Игра предлагает глубокую кастомизацию персонажей, богатый сюжет и множество возможностей для игры в одиночку или в группе.
|
||||
description_en=An MMORPG based on the works of J. R. R. Tolkien, allowing players to explore Middle-earth, take part in epic quests and battles with famous characters and creatures from the Lord of the Rings universe. The game offers deep customization of characters, a rich plot and many opportunities to play alone or in a group.
|
||||
|
Before Width: | Height: | Size: 606 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Metal War Online
|
||||
description_ru=Многопользовательская онлайн-игра в жанре экшен с элементами стратегии, где игроки управляют боевыми роботами и сражаются на различных аренах. Игра предлагает богатую кастомизацию техники и разнообразные игровые режимы, позволяя каждому игроку создавать уникальный стиль боя.
|
||||
description_en=A multiplayer online action game with strategic elements, where players control combat robots and battle on various arenas. The game offers extensive customization options for the machines and a variety of game modes, allowing each player to create a unique fighting style.
|
||||
|
Before Width: | Height: | Size: 684 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Mini World
|
||||
description_ru=Песочница, в которой игроки могут исследовать, строить и создавать свои уникальные миры в 3D-окружении. Игра предлагает разнообразные режимы, включая выживание и творчество, а также возможность взаимодействовать с другими игроками в многопользовательском формате.
|
||||
description_en=A sandbox game where players can explore, build, and create their unique worlds in a 3D environment. The game offers various modes, including survival and creative, as well as the ability to interact with other players in a multiplayer format.
|
||||
|
Before Width: | Height: | Size: 670 KiB |
@@ -1,3 +0,0 @@
|
||||
name=The World of Nifty Craft (ENG)
|
||||
description_ru=Многопользовательская онлайн-игра в жанре песочницы, где игроки могут строить, исследовать и развиваться в ярком и разнообразном мире, наполненном приключениями. Игра предлагает систему крафта, уникальные возможности для творчества и взаимодействия с другими игроками.
|
||||
description_en=A multiplayer online sandbox game where players can build, explore, and progress in a vibrant and diverse world filled with adventures. The game features a crafting system, unique opportunities for creativity, and interaction with other players.
|
||||
|
Before Width: | Height: | Size: 58 KiB |
@@ -1,3 +0,0 @@
|
||||
name=OSU
|
||||
description_ru=Ритм-игра, в которой игроки нажимают кружки, следуя музыке и ритму. Игра предлагает различные режимы и комьюнити, где пользователи могут создавать и делиться собственными картами.
|
||||
description_en=A rhythm game where players click circles in time with music and rhythm. The game features various modes and a community where users can create and share their own maps.
|
||||
|
Before Width: | Height: | Size: 191 KiB |
@@ -1,3 +0,0 @@
|
||||
name=PPSSPP
|
||||
description_ru=Эмулятор портативной игровой консоли PlayStation Portable (PSP), позволяющий запускать игры PSP на компьютерах и мобильных устройствах. Он поддерживает улучшенную графику и различные настройки, что позволяет наслаждаться классическими играми с высоким качеством.
|
||||
description_en=An emulator for the PlayStation Portable (PSP) handheld gaming console, enabling users to play PSP games on computers and mobile devices. It supports enhanced graphics and various settings, allowing players to enjoy classic games at high quality.
|
||||
|
Before Width: | Height: | Size: 814 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Panzar
|
||||
description_ru=Многопользовательская онлайн-игра в жанре экшен, где игроки сражаются в командах, используя различные классы персонажей и уникальные способности. Игра предлагает тактические сражения на больших картах с разрушительным взаимодействием окружения.
|
||||
description_en=A multiplayer online action game where players battle in teams using various character classes and unique abilities. The game features tactical battles on large maps with destructible environments.
|
||||
|
Before Width: | Height: | Size: 824 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Path of Exile
|
||||
description_ru=Бесплатная онлайн-игра в жанре action RPG, в которой игроки исследуют мрачный мир, сражаются с монстрами и развивают свои персонажи через глубокую систему навыков и предметов. Игра известна своим сложным геймплеем и постоянно обновляемым контентом, включая сезонные лиги и уникальные механики.
|
||||
description_en=A free online action RPG where players explore a dark world, fight monsters, and develop their characters through a deep skill and item system. The game is known for its challenging gameplay and constantly updated content, including seasonal leagues and unique mechanics.
|
||||
|
Before Width: | Height: | Size: 511 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Plarium Play
|
||||
description_ru=Лаунчер для игр Plarium.
|
||||
description_en=Launcher for Plarium studio games.
|
||||
|
Before Width: | Height: | Size: 526 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Project64
|
||||
description_ru=Эмулятор Nintendo 64, который позволяет играть в игры этой консоли на современных устройствах. Поддерживает различные настройки графики и управления, обеспечивая высококачественный игровой опыт.
|
||||
description_en=The Nintendo 64 emulator, which allows you to play games of this console on modern devices. Supports various graphics and control settings, providing a high-quality gaming experience.
|
||||
|
Before Width: | Height: | Size: 604 KiB |
@@ -1,3 +0,0 @@
|
||||
name=Russian Fishing 4
|
||||
description_ru=Реалистичный симулятор рыбалки, который предлагает игрокам погрузиться в увлекательный процесс ловли рыбы в различных красивых водоемах России. Игра включает в себя широкий выбор снастей, видов рыб и возможностей для соревнований с другими рыбаками.
|
||||
description_en=A realistic fishing simulator that immerses players in the enjoyable process of catching fish in various beautiful bodies of water across Russia. The game features a wide selection of tackle, fish species, and opportunities for competition with other anglers.
|
||||
|
Before Width: | Height: | Size: 557 KiB |