forked from Boria138/PortProtonQt
Compare commits
25 Commits
main
...
debian-tes
| Author | SHA1 | Date | |
|---|---|---|---|
|
de229401a7
|
|||
|
6f82068864
|
|||
|
d4672ecb0e
|
|||
|
|
087ac8eda2 | ||
|
|
0a9acaf5da | ||
|
d0fad6a3c9
|
|||
|
468887110c
|
|||
|
32e4950a00
|
|||
|
b16074fa5c
|
|||
|
1bd7c23419
|
|||
|
f4275dd465
|
|||
|
c8b91c4687
|
|||
|
4aaeb2e809
|
|||
|
b6ea9350fa
|
|||
|
29d25cec01
|
|||
|
a634de5462
|
|||
|
1ba1781994
|
|||
|
0aae292f61
|
|||
|
3ef433af0c
|
|||
|
|
9fe33e02d8 | ||
|
2ac91a759d
|
|||
|
2c82bff204
|
|||
|
0889aa883e
|
|||
|
7780dcfc4d
|
|||
|
9ef39ae2b6
|
@@ -1,4 +1,4 @@
|
|||||||
name: Nightly Build - AppImage, Arch, Fedora
|
name: Nightly Build - AppImage, Debian, Arch, Fedora
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -8,11 +8,37 @@ env:
|
|||||||
PACKAGE: "portprotonqt"
|
PACKAGE: "portprotonqt"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build-debian:
|
||||||
|
name: Build Debian Package
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
|
- name: Install required dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3-all python3-setuptools python3-build python3-installer dh-python debhelper devscripts build-essential python3-dev pybuild-plugin-pyproject
|
||||||
|
|
||||||
|
- name: Build Debian package
|
||||||
|
run: |
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
ls -la ../*.deb
|
||||||
|
# Copy Debian packages to a consistent location for upload
|
||||||
|
mkdir -p ./dist
|
||||||
|
cp ../*.deb ./dist/ || true
|
||||||
|
|
||||||
|
- name: Upload Debian package
|
||||||
|
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PortProtonQt-Debian
|
||||||
|
path: |
|
||||||
|
dist/*.deb
|
||||||
|
|
||||||
build-appimage:
|
build-appimage:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Install required dependencies
|
- name: Install required dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -73,7 +99,7 @@ jobs:
|
|||||||
echo '%_topdir /home/rpmbuild' > /home/rpmbuild/.rpmmacros
|
echo '%_topdir /home/rpmbuild' > /home/rpmbuild/.rpmmacros
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Copy fedora.spec
|
- name: Copy fedora.spec
|
||||||
run: |
|
run: |
|
||||||
@@ -134,7 +160,7 @@ jobs:
|
|||||||
su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git"
|
su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git"
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Upload Arch package
|
- name: Upload Arch package
|
||||||
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: Build AppImage, Arch and Fedora Packages
|
name: Build AppImage, Debian, Arch and Fedora Packages
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -8,12 +8,38 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
# Common version, will be used for tagging the release
|
# Common version, will be used for tagging the release
|
||||||
VERSION: 0.1.8
|
VERSION: 0.1.9
|
||||||
PKGDEST: "/tmp/portprotonqt"
|
PKGDEST: "/tmp/portprotonqt"
|
||||||
PACKAGE: "portprotonqt"
|
PACKAGE: "portprotonqt"
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
build-debian:
|
||||||
|
name: Build Debian Package
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install required dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3-all python3-setuptools python3-build python3-installer dh-python debhelper devscripts build-essential python3-dev pybuild-plugin-pyproject
|
||||||
|
|
||||||
|
- name: Build Debian package
|
||||||
|
run: |
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
ls -la ../*.deb
|
||||||
|
# Copy Debian packages to a consistent location for upload
|
||||||
|
mkdir -p ./dist
|
||||||
|
cp ../*.deb ./dist/ || true
|
||||||
|
|
||||||
|
- name: Upload Debian package
|
||||||
|
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PortProtonQt-Debian
|
||||||
|
path: |
|
||||||
|
dist/*.deb
|
||||||
|
|
||||||
build-appimage:
|
build-appimage:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -149,7 +175,7 @@ jobs:
|
|||||||
|
|
||||||
release:
|
release:
|
||||||
name: Create and Publish Release
|
name: Create and Publish Release
|
||||||
needs: [build-appimage, build-arch, build-fedora]
|
needs: [build-debian, build-appimage, build-arch, build-fedora]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: https://gitea.com/actions/checkout@v4
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: https://gitea.com/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
uses: https://gitea.com/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
name: Build Check - AppImage, Arch, Fedora
|
name: Build Check - AppImage, Debian, Arch, Fedora
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'build-aux/**'
|
- 'build-aux/**'
|
||||||
|
- 'debian/**'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
PKGDEST: "/tmp/portprotonqt"
|
PKGDEST: "/tmp/portprotonqt"
|
||||||
@@ -15,10 +16,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
appimage: ${{ steps.check.outputs.appimage }}
|
appimage: ${{ steps.check.outputs.appimage }}
|
||||||
|
debian: ${{ steps.check.outputs.debian }}
|
||||||
fedora: ${{ steps.check.outputs.fedora }}
|
fedora: ${{ steps.check.outputs.fedora }}
|
||||||
arch: ${{ steps.check.outputs.arch }}
|
arch: ${{ steps.check.outputs.arch }}
|
||||||
steps:
|
steps:
|
||||||
- uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -43,6 +45,13 @@ jobs:
|
|||||||
echo "appimage=false" >> $GITHUB_OUTPUT
|
echo "appimage=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check Debian directory
|
||||||
|
if grep -q "debian/" changed_files.txt || ls debian/ 1> /dev/null 2>&1; then
|
||||||
|
echo "debian=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "debian=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
# Check Fedora spec files (only fedora-git.spec)
|
# Check Fedora spec files (only fedora-git.spec)
|
||||||
if grep -q "build-aux/fedora-git.spec" changed_files.txt; then
|
if grep -q "build-aux/fedora-git.spec" changed_files.txt; then
|
||||||
echo "fedora=true" >> $GITHUB_OUTPUT
|
echo "fedora=true" >> $GITHUB_OUTPUT
|
||||||
@@ -57,13 +66,38 @@ jobs:
|
|||||||
echo "arch=false" >> $GITHUB_OUTPUT
|
echo "arch=false" >> $GITHUB_OUTPUT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
build-debian:
|
||||||
|
name: Build Debian Package
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs: changes
|
||||||
|
if: needs.changes.outputs.debian == 'true' || github.event_name == 'workflow_dispatch'
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
|
- name: Install required dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y python3-all python3-setuptools python3-build python3-installer dh-python debhelper devscripts build-essential python3-dev pybuild-plugin-pyproject
|
||||||
|
|
||||||
|
- name: Build Debian package
|
||||||
|
run: |
|
||||||
|
dpkg-buildpackage -us -uc -b
|
||||||
|
ls -la ../*.deb
|
||||||
|
|
||||||
|
- name: Upload Debian package
|
||||||
|
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: PortProtonQt-Debian
|
||||||
|
path: |
|
||||||
|
../*.deb
|
||||||
|
|
||||||
build-appimage:
|
build-appimage:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: changes
|
needs: changes
|
||||||
if: needs.changes.outputs.appimage == 'true' || github.event_name == 'workflow_dispatch'
|
if: needs.changes.outputs.appimage == 'true' || github.event_name == 'workflow_dispatch'
|
||||||
steps:
|
steps:
|
||||||
- uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Install required dependencies
|
- name: Install required dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -115,7 +149,7 @@ jobs:
|
|||||||
echo '%_topdir /home/rpmbuild' > /home/rpmbuild/.rpmmacros
|
echo '%_topdir /home/rpmbuild' > /home/rpmbuild/.rpmmacros
|
||||||
|
|
||||||
- name: Checkout repo
|
- name: Checkout repo
|
||||||
uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Copy fedora-git.spec
|
- name: Copy fedora-git.spec
|
||||||
run: |
|
run: |
|
||||||
@@ -178,7 +212,7 @@ jobs:
|
|||||||
su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git"
|
su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git"
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Upload Arch package
|
- name: Upload Arch package
|
||||||
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ jobs:
|
|||||||
name: Check code
|
name: Check code
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: https://gitea.com/actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
uses: https://gitea.com/actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: https://gitea.com/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
uses: https://gitea.com/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: ghcr.io/renovatebot/renovate:latest@sha256:17c8966ef38fc361e108a550ffe2dcedf73e846f9975a974aea3d48c66b107a6
|
container: ghcr.io/renovatebot/renovate:latest@sha256:17c8966ef38fc361e108a550ffe2dcedf73e846f9975a974aea3d48c66b107a6
|
||||||
steps:
|
steps:
|
||||||
- uses: https://gitea.com/actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: https://gitea.com/actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6
|
uses: https://gitea.com/actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6
|
||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Все заметные изменения в этом проекте фиксируются в этом файле.
|
Все заметные изменения в этом проекте фиксируются в этом файле.
|
||||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
|
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
|
||||||
|
|
||||||
## [Unreleased]
|
## [0.1.9] - 2025-12-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Добавлены основные и расширенные настройки для `.exe`-файлов
|
- Добавлены основные и расширенные настройки для `.exe`-файлов
|
||||||
@@ -18,11 +18,16 @@
|
|||||||
- Ускорено чтение конфигов за счёт уменьшения количества обращений к файловой системе.
|
- Ускорено чтение конфигов за счёт уменьшения количества обращений к файловой системе.
|
||||||
- Из стандартной темы удалены неиспользуемые шрифты
|
- Из стандартной темы удалены неиспользуемые шрифты
|
||||||
- Улучшена совместимость с Qt 6.10
|
- Улучшена совместимость с Qt 6.10
|
||||||
|
- Ускорен запуск программы
|
||||||
|
- В диалог редактирования ярылыка добавлен placeholder с уточнением того что в качевстве обложки можно использовать и ссылку, а не только файл
|
||||||
|
- Ссылку на обложку в диалоге редактирования ярлыка теперь можно указывать без протокола вроде http или https
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Добавлено больше проверок на None для избежания вылетов
|
- Добавлено больше проверок на None для избежания вылетов
|
||||||
- Улучшена работа с потоками для избежания вылетов
|
- Улучшена работа с потоками для избежания вылетов
|
||||||
- Исправлен запуск PortProton из Flatpak: теперь используется `flatpak run`, а не `start.sh`
|
- Исправлен запуск PortProton из Flatpak: теперь используется `flatpak run`, а не `start.sh`
|
||||||
|
- Исправлено применение обложки по ссылке например со steamgriddb.com/
|
||||||
|
- Исправлено множественное открытие окон в X11
|
||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
- @Vector_null
|
- @Vector_null
|
||||||
|
|||||||
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
recursive-include portprotonqt/themes *
|
||||||
|
recursive-include portprotonqt/locales *
|
||||||
@@ -5,8 +5,7 @@ script:
|
|||||||
- uv venv
|
- uv venv
|
||||||
- uv pip install --no-cache-dir ../
|
- uv pip install --no-cache-dir ../
|
||||||
- cp -r .venv/lib/python3.10/site-packages/* AppDir/usr/local/lib/python3.10/dist-packages
|
- cp -r .venv/lib/python3.10/site-packages/* AppDir/usr/local/lib/python3.10/dist-packages
|
||||||
- cp -r share AppDir/usr
|
- cp -r usr AppDir/
|
||||||
- cp -r lib AppDir/usr
|
|
||||||
- rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/qml/
|
- rm -rf AppDir/usr/local/lib/python3.10/dist-packages/PySide6/Qt/qml/
|
||||||
- rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{assistant,designer,linguist,lrelease,lupdate}
|
- rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{assistant,designer,linguist,lrelease,lupdate}
|
||||||
- rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{Qt3DAnimation*,Qt3DCore*,Qt3DExtras*,Qt3DInput*,Qt3DLogic*,Qt3DRender*,QtBluetooth*,QtCharts*,QtConcurrent*,QtDataVisualization*,QtDesigner*,QtExampleIcons*,QtGraphs*,QtGraphsWidgets*,QtHelp*,QtHttpServer*,QtLocation*,QtMultimedia*,QtMultimediaWidgets*,QtNetworkAuth*,QtNfc*,QtOpenGL*,QtOpenGLWidgets*,QtPdf*,QtPdfWidgets*,QtPositioning*,QtPrintSupport*,QtQml*,QtQuick*,QtQuick3D*,QtQuickControls2*,QtQuickTest*,QtQuickWidgets*,QtRemoteObjects*,QtScxml*,QtSensors*,QtSerialBus*,QtSerialPort*,QtSpatialAudio*,QtSql*,QtStateMachine*,QtSvgWidgets*,QtTest*,QtTextToSpeech*,QtUiTools*,QtWebChannel*,QtWebEngineCore*,QtWebEngineQuick*,QtWebEngineWidgets*,QtWebSockets*,QtWebView*,QtXml*}
|
- rm -f AppDir/usr/local/lib/python3.10/dist-packages/PySide6/{Qt3DAnimation*,Qt3DCore*,Qt3DExtras*,Qt3DInput*,Qt3DLogic*,Qt3DRender*,QtBluetooth*,QtCharts*,QtConcurrent*,QtDataVisualization*,QtDesigner*,QtExampleIcons*,QtGraphs*,QtGraphsWidgets*,QtHelp*,QtHttpServer*,QtLocation*,QtMultimedia*,QtMultimediaWidgets*,QtNetworkAuth*,QtNfc*,QtOpenGL*,QtOpenGLWidgets*,QtPdf*,QtPdfWidgets*,QtPositioning*,QtPrintSupport*,QtQml*,QtQuick*,QtQuick3D*,QtQuickControls2*,QtQuickTest*,QtQuickWidgets*,QtRemoteObjects*,QtScxml*,QtSensors*,QtSerialBus*,QtSerialPort*,QtSpatialAudio*,QtSql*,QtStateMachine*,QtSvgWidgets*,QtTest*,QtTextToSpeech*,QtUiTools*,QtWebChannel*,QtWebEngineCore*,QtWebEngineQuick*,QtWebEngineWidgets*,QtWebSockets*,QtWebView*,QtXml*}
|
||||||
@@ -37,7 +36,7 @@ AppDir:
|
|||||||
id: ru.linux_gaming.PortProtonQt
|
id: ru.linux_gaming.PortProtonQt
|
||||||
name: PortProtonQt
|
name: PortProtonQt
|
||||||
icon: ru.linux_gaming.PortProtonQt
|
icon: ru.linux_gaming.PortProtonQt
|
||||||
version: 0.1.8
|
version: 0.1.9
|
||||||
exec: usr/bin/python3
|
exec: usr/bin/python3
|
||||||
exec_args: "-m portprotonqt.app $@"
|
exec_args: "-m portprotonqt.app $@"
|
||||||
apt:
|
apt:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
pkgname=portprotonqt
|
pkgname=portprotonqt
|
||||||
pkgver=0.1.8
|
pkgver=0.1.9
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store"
|
pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store"
|
||||||
arch=('any')
|
arch=('any')
|
||||||
@@ -19,6 +19,5 @@ build() {
|
|||||||
package() {
|
package() {
|
||||||
cd "$srcdir/PortProtonQt"
|
cd "$srcdir/PortProtonQt"
|
||||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
python -m installer --destdir="$pkgdir" dist/*.whl
|
||||||
cp -r build-aux/share "$pkgdir/usr/"
|
cp -r build-aux/usr "$pkgdir/"
|
||||||
cp -r build-aux/lib "$pkgdir/usr/"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,5 @@ build() {
|
|||||||
package() {
|
package() {
|
||||||
cd "$srcdir/PortProtonQt"
|
cd "$srcdir/PortProtonQt"
|
||||||
python -m installer --destdir="$pkgdir" dist/*.whl
|
python -m installer --destdir="$pkgdir" dist/*.whl
|
||||||
cp -r build-aux/share "$pkgdir/usr/"
|
cp -r build-aux/usr "$pkgdir/"
|
||||||
cp -r build-aux/lib "$pkgdir/usr/"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,8 +70,7 @@ cd %{oname}
|
|||||||
cd %{oname}
|
cd %{oname}
|
||||||
%pyproject_install
|
%pyproject_install
|
||||||
%pyproject_save_files %{pypi_name}
|
%pyproject_save_files %{pypi_name}
|
||||||
cp -r build-aux/share %{buildroot}/usr/
|
cp -r build-aux/usr %{buildroot}/
|
||||||
cp -r build-aux/lib %{buildroot}/usr/
|
|
||||||
|
|
||||||
%files -n python3-%{pypi_name}-git -f %{pyproject_files}
|
%files -n python3-%{pypi_name}-git -f %{pyproject_files}
|
||||||
%{_bindir}/%{pypi_name}
|
%{_bindir}/%{pypi_name}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
%global pypi_name portprotonqt
|
%global pypi_name portprotonqt
|
||||||
%global pypi_version 0.1.8
|
%global pypi_version 0.1.9
|
||||||
%global oname PortProtonQt
|
%global oname PortProtonQt
|
||||||
%global _python_no_extras_requires 1
|
%global _python_no_extras_requires 1
|
||||||
|
|
||||||
@@ -69,8 +69,7 @@ cd %{oname}
|
|||||||
cd %{oname}
|
cd %{oname}
|
||||||
%pyproject_install
|
%pyproject_install
|
||||||
%pyproject_save_files %{pypi_name}
|
%pyproject_save_files %{pypi_name}
|
||||||
cp -r build-aux/share %{buildroot}/usr/
|
cp -r build-aux/usr %{buildroot}/
|
||||||
cp -r build-aux/lib %{buildroot}/usr/
|
|
||||||
|
|
||||||
%files -n python3-%{pypi_name} -f %{pyproject_files}
|
%files -n python3-%{pypi_name} -f %{pyproject_files}
|
||||||
%{_bindir}/%{pypi_name}
|
%{_bindir}/%{pypi_name}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@@ -1373,7 +1373,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"normalized_name": "arena breakout infinite",
|
"normalized_name": "arena breakout infinite",
|
||||||
"status": "Broken"
|
"status": "Denied"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"normalized_name": "pixel gun 3d pc",
|
"normalized_name": "pixel gun 3d pc",
|
||||||
@@ -4316,7 +4316,7 @@
|
|||||||
"status": "Broken"
|
"status": "Broken"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"normalized_name": "solo leveling arise",
|
"normalized_name": "solo leveling arise overdrive",
|
||||||
"status": "Running"
|
"status": "Running"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -4527,10 +4527,6 @@
|
|||||||
"normalized_name": "project wraith",
|
"normalized_name": "project wraith",
|
||||||
"status": "Broken"
|
"status": "Broken"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"normalized_name": "solo leveling arise",
|
|
||||||
"status": "Broken"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"normalized_name": "freedom wars",
|
"normalized_name": "freedom wars",
|
||||||
"status": "Running"
|
"status": "Running"
|
||||||
@@ -4542,5 +4538,9 @@
|
|||||||
{
|
{
|
||||||
"normalized_name": "no more room in hell 2",
|
"normalized_name": "no more room in hell 2",
|
||||||
"status": "Running"
|
"status": "Running"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_name": "call of duty black ops 7",
|
||||||
|
"status": "Denied"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
Binary file not shown.
13130
data/games_appid.json
13130
data/games_appid.json
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -1,4 +1,128 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"normalized_title": "metal gear solid v the phantom pain",
|
||||||
|
"slug": "metal-gear-solid-v-the-phantom-pain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "battlefield bad company 2",
|
||||||
|
"slug": "battlefield-bad-company-2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "call of duty black ops",
|
||||||
|
"slug": "call-of-duty-black-ops"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "call of duty modern warfare 2 (2009)",
|
||||||
|
"slug": "call-of-duty-modern-warfare-2-2009"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "call of duty black ops cold war",
|
||||||
|
"slug": "call-of-duty-black-ops-cold-war"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "call of duty infinite warfare",
|
||||||
|
"slug": "call-of-duty-infinite-warfare"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "lost planet 2",
|
||||||
|
"slug": "lost-planet-2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "lost planet extreme condition colonies",
|
||||||
|
"slug": "lost-planet-extreme-condition-colonies-edition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "starcraft",
|
||||||
|
"slug": "starcraft-remastered"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "the entropy centre",
|
||||||
|
"slug": "the-entropy-centre"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "metal gear solid v ground zeroes",
|
||||||
|
"slug": "metal-gear-solid-v-ground-zeroes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "escape from tarkov",
|
||||||
|
"slug": "escape-from-tarkov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "command & conquer generals",
|
||||||
|
"slug": "command-conquer-generals"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "command & conquer generals zero hour",
|
||||||
|
"slug": "command-conquer-generals-zero-hour"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "absolum",
|
||||||
|
"slug": "absolum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "tom clancy's splinter cell chaos theory",
|
||||||
|
"slug": "tom-clancys-splinter-cell-chaos-theory"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "winter burrow",
|
||||||
|
"slug": "winter-burrow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "forager",
|
||||||
|
"slug": "forager"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "wall world",
|
||||||
|
"slug": "wall-world"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "grand theft auto iv the",
|
||||||
|
"slug": "grand-theft-auto-iv-the-complete-edition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "voidtrain",
|
||||||
|
"slug": "voidtrain"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "jdm japanese drift master",
|
||||||
|
"slug": "jdm-japanese-drift-master"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "lego harry potter collection",
|
||||||
|
"slug": "lego-harry-potter-collection"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "life is strange season",
|
||||||
|
"slug": "life-is-strange-complete-season"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "земский собор [демо]",
|
||||||
|
"slug": "zemskij-sobor-demo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "syberia",
|
||||||
|
"slug": "syberia-remastered"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "europa universalis v",
|
||||||
|
"slug": "europa-universalis-v"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "no i'm not a human",
|
||||||
|
"slug": "no-im-not-a-human"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "dispatch digital deluxe",
|
||||||
|
"slug": "dispatch-digital-deluxe-edition"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "cossacks 3 digital deluxe",
|
||||||
|
"slug": "cossacks-3-digital-deluxe"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"normalized_title": "battlefield 2",
|
||||||
|
"slug": "battlefield-2"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"normalized_title": "split/second",
|
"normalized_title": "split/second",
|
||||||
"slug": "split-second"
|
"slug": "split-second"
|
||||||
@@ -11,10 +135,6 @@
|
|||||||
"normalized_title": "foundation",
|
"normalized_title": "foundation",
|
||||||
"slug": "foundation"
|
"slug": "foundation"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"normalized_title": "земский собор [демо]",
|
|
||||||
"slug": "zemskij-sobor-demo"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"normalized_title": "crusader kings 3",
|
"normalized_title": "crusader kings 3",
|
||||||
"slug": "crusader-kings-3"
|
"slug": "crusader-kings-3"
|
||||||
@@ -1411,10 +1531,6 @@
|
|||||||
"normalized_title": "world of sea battle",
|
"normalized_title": "world of sea battle",
|
||||||
"slug": "world-of-sea-battle"
|
"slug": "world-of-sea-battle"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"normalized_title": "escape from tarkov",
|
|
||||||
"slug": "escape-from-tarkov"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"normalized_title": "bayonetta",
|
"normalized_title": "bayonetta",
|
||||||
"slug": "bayonetta"
|
"slug": "bayonetta"
|
||||||
@@ -1539,10 +1655,6 @@
|
|||||||
"normalized_title": "call of duty 2",
|
"normalized_title": "call of duty 2",
|
||||||
"slug": "call-of-duty-2"
|
"slug": "call-of-duty-2"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"normalized_title": "call of duty infinite warfare",
|
|
||||||
"slug": "call-of-duty-infinite-warfare"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"normalized_title": "call of duty world at war",
|
"normalized_title": "call of duty world at war",
|
||||||
"slug": "call-of-duty-world-at-war"
|
"slug": "call-of-duty-world-at-war"
|
||||||
@@ -1735,10 +1847,6 @@
|
|||||||
"normalized_title": "elden ring",
|
"normalized_title": "elden ring",
|
||||||
"slug": "elden-ring"
|
"slug": "elden-ring"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"normalized_title": "starcraft",
|
|
||||||
"slug": "starcraft-remastered"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"normalized_title": "cataclismo",
|
"normalized_title": "cataclismo",
|
||||||
"slug": "cataclismo"
|
"slug": "cataclismo"
|
||||||
|
|||||||
Binary file not shown.
8
debian/README.Debian
vendored
Normal file
8
debian/README.Debian
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
PortProtonQt for Debian
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
This package provides a modern GUI for managing and launching games from
|
||||||
|
PortProton, Steam, and Epic Games Store.
|
||||||
|
|
||||||
|
For more information about PortProtonQt, please see the project homepage:
|
||||||
|
https://git.linux-gaming.ru/Boria138/PortProtonQt
|
||||||
22
debian/changelog
vendored
Normal file
22
debian/changelog
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
portprotonqt (0.1.9-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Добавлены основные и расширенные настройки для ".exe"-файлов
|
||||||
|
* Добавлена кнопка обновления сетки без необходимости перезапуска PortProtonQt (F5 на клавиатуре, GUIDE + Select на геймпаде)
|
||||||
|
* Добавлена эмуляция мыши по GUIDE (Xbox или PS) + Start для установки приложений или взаимодействия с инструментами Wine не адаптированные под геймпад (работает только если PortProtonQt вне фокуса)
|
||||||
|
* При сворачивании приложения в трей оно теперь корректно восстанавливается, вместо запуска нового экземпляра
|
||||||
|
* Добавлена поддержка SteamGridDB в качестве дополнительного источника обложек
|
||||||
|
* При добавлении карточки в избранное она автоматически становится первой без необходимости перезапуска
|
||||||
|
* Изменено оформление виртуальной клавиатуры для лучшего соответствия общей теме
|
||||||
|
* Ускорено чтение конфигов за счёт уменьшения количества обращений к файловой системе.
|
||||||
|
* Из стандартной темы удалены неиспользуемые шрифты
|
||||||
|
* Улучшена совместимость с Qt 6.10
|
||||||
|
* Ускорен запуск программы
|
||||||
|
* В диалог редактирования ярылыка добавлен placeholder с уточнением того что в качевстве обложки можно использовать и ссылку, а не только файл
|
||||||
|
* Ссылку на обложку в диалоге редактирования ярлыка теперь можно указывать без протокола вроде http или https
|
||||||
|
* Добавлено больше проверок на None для избежания вылетов
|
||||||
|
* Улучшена работа с потоками для избежания вылетов
|
||||||
|
* Исправлен запуск PortProton из Flatpak: теперь используется "flatpak run", а не "start.sh"
|
||||||
|
* Исправлено применение обложки по ссылке например со steamgriddb.com/
|
||||||
|
* Исправлено множественное открытие окон в X11
|
||||||
|
|
||||||
|
-- Boris Yumankulov <boria138@altlinux.org> Mon, 08 Dec 2025 00:00:00 +0000
|
||||||
5
debian/changelog.bak
vendored
Normal file
5
debian/changelog.bak
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
portprotonqt (0.1.9-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release of PortProtonQt for Debian
|
||||||
|
|
||||||
|
-- Boris Yumankulov <boria138@altlinux.org> Thu, 11 Dec 2025 00:00:00 +0000
|
||||||
1
debian/compat
vendored
Normal file
1
debian/compat
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
13
|
||||||
47
debian/control
vendored
Normal file
47
debian/control
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
Source: portprotonqt
|
||||||
|
Priority: optional
|
||||||
|
Maintainer: Boris Yumankulov <boria138@altlinux.org>
|
||||||
|
Build-Depends: debhelper (>= 13),
|
||||||
|
dh-python,
|
||||||
|
python3-all,
|
||||||
|
python3-setuptools,
|
||||||
|
python3-build,
|
||||||
|
python3-installer,
|
||||||
|
pybuild-plugin-pyproject
|
||||||
|
Standards-Version: 4.6.0
|
||||||
|
Homepage: https://git.linux-gaming.ru/Boria138/PortProtonQt
|
||||||
|
|
||||||
|
Package: python3-portprotonqt
|
||||||
|
Architecture: all
|
||||||
|
Depends: ${python3:Depends},
|
||||||
|
${misc:Depends},
|
||||||
|
python3-babel,
|
||||||
|
python3-beautifulsoup4,
|
||||||
|
python3-evdev,
|
||||||
|
python3-icoextract,
|
||||||
|
python3-numpy,
|
||||||
|
python3-orjson,
|
||||||
|
python3-pillow,
|
||||||
|
python3-psutil,
|
||||||
|
python3-pyside6,
|
||||||
|
python3-pyudev,
|
||||||
|
python3-rapidfuzz,
|
||||||
|
python3-requests,
|
||||||
|
python3-tqdm,
|
||||||
|
python3-vdf,
|
||||||
|
python3-websocket-client,
|
||||||
|
perl-image-exiftool,
|
||||||
|
xdg-utils,
|
||||||
|
cabextract,
|
||||||
|
gzip,
|
||||||
|
unzip,
|
||||||
|
curl,
|
||||||
|
unrar
|
||||||
|
Description: Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store
|
||||||
|
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.
|
||||||
7
debian/copyright
vendored
Normal file
7
debian/copyright
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: PortProtonQt
|
||||||
|
Source: https://git.linux-gaming.ru/Boria138/PortProtonQt
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: 2024-2025 Boris Yumankulov <boria138@altlinux.org>
|
||||||
|
License: GPL-3.0+
|
||||||
33
debian/rules
vendored
Executable file
33
debian/rules
vendored
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
|
export PYBUILD_NAME=portprotonqt
|
||||||
|
export DEB_BUILD_OPTIONS=nocheck
|
||||||
|
export DH_VERBOSE=1
|
||||||
|
|
||||||
|
%:
|
||||||
|
dh $@ --with python3 --buildsystem=pybuild
|
||||||
|
|
||||||
|
override_dh_install:
|
||||||
|
dh_install
|
||||||
|
# Create necessary directories
|
||||||
|
mkdir -p debian/python3-portprotonqt/usr/lib/udev/rules.d
|
||||||
|
mkdir -p debian/python3-portprotonqt/usr/share/applications
|
||||||
|
mkdir -p debian/python3-portprotonqt/usr/share/bash-completion/completions
|
||||||
|
mkdir -p debian/python3-portprotonqt/usr/share/icons/hicolor/scalable/apps
|
||||||
|
mkdir -p debian/python3-portprotonqt/usr/share/metainfo
|
||||||
|
# Copy additional files from build-aux/usr (if they exist)
|
||||||
|
if [ -d "$(CURDIR)/build-aux/usr/lib/udev/rules.d" ]; then \
|
||||||
|
cp -r $(CURDIR)/build-aux/usr/lib/udev/rules.d/* debian/python3-portprotonqt/usr/lib/udev/rules.d/ || true; \
|
||||||
|
fi
|
||||||
|
if [ -d "$(CURDIR)/build-aux/usr/share/applications" ]; then \
|
||||||
|
cp -r $(CURDIR)/build-aux/usr/share/applications/* debian/python3-portprotonqt/usr/share/applications/ || true; \
|
||||||
|
fi
|
||||||
|
if [ -d "$(CURDIR)/build-aux/usr/share/bash-completion/completions" ]; then \
|
||||||
|
cp -r $(CURDIR)/build-aux/usr/share/bash-completion/completions/* debian/python3-portprotonqt/usr/share/bash-completion/completions/ || true; \
|
||||||
|
fi
|
||||||
|
if [ -d "$(CURDIR)/build-aux/usr/share/icons/hicolor/scalable/apps" ]; then \
|
||||||
|
cp -r $(CURDIR)/build-aux/usr/share/icons/hicolor/scalable/apps/* debian/python3-portprotonqt/usr/share/icons/hicolor/scalable/apps/ || true; \
|
||||||
|
fi
|
||||||
|
if [ -d "$(CURDIR)/build-aux/usr/share/metainfo" ]; then \
|
||||||
|
cp -r $(CURDIR)/build-aux/usr/share/metainfo/* debian/python3-portprotonqt/usr/share/metainfo/ || true; \
|
||||||
|
fi
|
||||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.0 (native)
|
||||||
7
debian/source/options
vendored
Normal file
7
debian/source/options
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Configuration for Debian source package
|
||||||
|
compression = "gzip"
|
||||||
|
|
||||||
|
# Files and directories to exclude from source package
|
||||||
|
tar-ignore = "dev-scripts"
|
||||||
|
tar-ignore = ".*"
|
||||||
|
tar-ignore = "__pycache__"
|
||||||
@@ -4,7 +4,7 @@ import argparse
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import date
|
from datetime import date, datetime
|
||||||
|
|
||||||
# Base directory of the project
|
# Base directory of the project
|
||||||
BASE_DIR = Path(__file__).parent.parent
|
BASE_DIR = Path(__file__).parent.parent
|
||||||
@@ -16,6 +16,7 @@ PYPROJECT = BASE_DIR / "pyproject.toml"
|
|||||||
APP_PY = BASE_DIR / "portprotonqt" / "app.py"
|
APP_PY = BASE_DIR / "portprotonqt" / "app.py"
|
||||||
GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml"
|
GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml"
|
||||||
CHANGELOG = BASE_DIR / "CHANGELOG.md"
|
CHANGELOG = BASE_DIR / "CHANGELOG.md"
|
||||||
|
DEBIAN_CHANGELOG = BASE_DIR / "debian" / "changelog"
|
||||||
|
|
||||||
def bump_appimage(path: Path, old: str, new: str) -> bool:
|
def bump_appimage(path: Path, old: str, new: str) -> bool:
|
||||||
"""
|
"""
|
||||||
@@ -109,6 +110,138 @@ def bump_changelog(path: Path, old: str, new: str) -> bool:
|
|||||||
path.write_text(new_text, encoding='utf-8')
|
path.write_text(new_text, encoding='utf-8')
|
||||||
return bool(count)
|
return bool(count)
|
||||||
|
|
||||||
|
def bump_debian_changelog(path: Path, old: str, new: str) -> bool:
|
||||||
|
"""
|
||||||
|
Update debian/changelog with new version
|
||||||
|
"""
|
||||||
|
if not path.exists():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Extract changelog entries from CHANGELOG.md for this version
|
||||||
|
changelog_md_path = BASE_DIR / "CHANGELOG.md"
|
||||||
|
changelog_entries = []
|
||||||
|
changelog_date = None
|
||||||
|
|
||||||
|
if changelog_md_path.exists():
|
||||||
|
changelog_text = changelog_md_path.read_text(encoding='utf-8')
|
||||||
|
lines = changelog_text.splitlines()
|
||||||
|
|
||||||
|
# Find the section for the new version and extract the date
|
||||||
|
start_reading = False
|
||||||
|
end_reading = False
|
||||||
|
in_contributors_section = False
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
if line.startswith(f"## [{new}]"):
|
||||||
|
# Extract date from line like "## [0.1.9] - 2025-12-08"
|
||||||
|
date_match = re.search(r'\[.+\] - (\d{4}-\d{2}-\d{2})', line)
|
||||||
|
if date_match:
|
||||||
|
changelog_date_str = date_match.group(1)
|
||||||
|
# Convert to the expected Debian format
|
||||||
|
date_obj = datetime.strptime(changelog_date_str, '%Y-%m-%d')
|
||||||
|
changelog_date = date_obj.strftime('%a, %d %b %Y') + " 00:00:00 +0000"
|
||||||
|
|
||||||
|
start_reading = True
|
||||||
|
in_contributors_section = False
|
||||||
|
continue
|
||||||
|
elif line.startswith("## [") and start_reading:
|
||||||
|
end_reading = True
|
||||||
|
break
|
||||||
|
elif line.strip().lower() == "### contributors":
|
||||||
|
# Start of contributors section - skip following lines until next section
|
||||||
|
in_contributors_section = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip section headers and contributor sections
|
||||||
|
if start_reading and not end_reading and not in_contributors_section:
|
||||||
|
stripped_line = line.strip()
|
||||||
|
if stripped_line and not line.startswith("#") and not line.startswith("[") and not line.lower().startswith("###"):
|
||||||
|
# Check if this line is a list item with changes
|
||||||
|
if re.match(r'^\s*[*-]\s+', line):
|
||||||
|
# Remove markdown list formatting and add proper Debian format
|
||||||
|
clean_line = re.sub(r'^\s*[*-]\s+', ' * ', line.rstrip())
|
||||||
|
# Remove common markdown formatting like backticks
|
||||||
|
clean_line = re.sub(r'`([^`]+)`', r'"\1"', clean_line) # Replace `code` with "code"
|
||||||
|
changelog_entries.append(clean_line)
|
||||||
|
# Also include lines that are sub-items (indented changes)
|
||||||
|
elif line.startswith(" ") and re.match(r'^\s*[*-]\s+', line[4:]):
|
||||||
|
clean_line = re.sub(r'^\s*[*-]\s+', ' * ', line[4:].rstrip())
|
||||||
|
clean_line = " " + clean_line # Add extra indentation
|
||||||
|
# Remove common markdown formatting
|
||||||
|
clean_line = re.sub(r'`([^`]+)`', r'"\1"', clean_line)
|
||||||
|
changelog_entries.append(clean_line)
|
||||||
|
|
||||||
|
# If no specific entries found for this version, use generic message
|
||||||
|
if not changelog_entries:
|
||||||
|
changelog_entries = [" * New upstream release"]
|
||||||
|
|
||||||
|
# Use changelog date if available, otherwise use current time
|
||||||
|
current_time = changelog_date if changelog_date else datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0000')
|
||||||
|
|
||||||
|
# Read the existing changelog to get maintainer info and other fields
|
||||||
|
text = path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
# If the file is empty or doesn't contain proper maintainer info, use a default
|
||||||
|
lines = text.splitlines()
|
||||||
|
|
||||||
|
if not lines or not any(line.startswith(" -- ") for line in lines):
|
||||||
|
# Create a default changelog entry with proper format
|
||||||
|
package_name = "portprotonqt"
|
||||||
|
new_version_line = f"{package_name} ({new}-1) unstable; urgency=medium"
|
||||||
|
|
||||||
|
# Default maintainer info from the original file
|
||||||
|
default_maintainer = "Boris Yumankulov <boria138@altlinux.org>"
|
||||||
|
maintainer_line = f" -- {default_maintainer} {current_time}"
|
||||||
|
|
||||||
|
new_content = new_version_line + "\n\n" + "\n".join(changelog_entries) + "\n\n" + maintainer_line + "\n"
|
||||||
|
else:
|
||||||
|
# Extract the header template from the current first entry
|
||||||
|
header_parts = []
|
||||||
|
entry_end_index = 0
|
||||||
|
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
header_parts.append(line)
|
||||||
|
if line.startswith(" -- "):
|
||||||
|
entry_end_index = i + 1
|
||||||
|
break
|
||||||
|
|
||||||
|
# Construct new changelog entry
|
||||||
|
new_entry_lines = []
|
||||||
|
|
||||||
|
if header_parts:
|
||||||
|
# Parse the first line to extract package name (before the version)
|
||||||
|
first_line = header_parts[0]
|
||||||
|
# Extract package name by getting everything before the opening parenthesis
|
||||||
|
if '(' in first_line:
|
||||||
|
package_name = first_line.split('(')[0].strip()
|
||||||
|
new_version_line = f"{package_name} ({new}-1) unstable; urgency=medium"
|
||||||
|
else:
|
||||||
|
# Fallback: if no parentheses found, use a default format
|
||||||
|
new_version_line = f"portprotonqt ({new}-1) unstable; urgency=medium"
|
||||||
|
|
||||||
|
new_entry_lines.append(new_version_line)
|
||||||
|
|
||||||
|
# Add the changelog entries
|
||||||
|
new_entry_lines.extend(changelog_entries)
|
||||||
|
|
||||||
|
# Add the maintainer info and timestamp
|
||||||
|
for j in range(1, len(header_parts)):
|
||||||
|
if header_parts[j].startswith(" -- "):
|
||||||
|
# Extract the maintainer information (everything after "-- ")
|
||||||
|
maintainer_part = header_parts[j][4:] # Remove leading " -- "
|
||||||
|
# Extract only the name and email, ignore timestamp
|
||||||
|
maintainer_info = maintainer_part.split(' ')[0].strip()
|
||||||
|
new_entry_lines.append(f" -- {maintainer_info} {current_time}")
|
||||||
|
elif not header_parts[j].startswith(" *"): # Skip existing changes since we added new ones
|
||||||
|
new_entry_lines.append(header_parts[j])
|
||||||
|
|
||||||
|
# Reconstruct the file with new entry at the top followed by the rest
|
||||||
|
new_content = '\n'.join(new_entry_lines) + '\n' + '\n'.join(lines[entry_end_index:])
|
||||||
|
|
||||||
|
path.write_text(new_content, encoding='utf-8')
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description='Bump project version in specific files')
|
parser = argparse.ArgumentParser(description='Bump project version in specific files')
|
||||||
parser.add_argument('old', help='Old version string')
|
parser.add_argument('old', help='Old version string')
|
||||||
@@ -123,7 +256,8 @@ def main():
|
|||||||
(PYPROJECT, bump_pyproject),
|
(PYPROJECT, bump_pyproject),
|
||||||
(APP_PY, bump_app_py),
|
(APP_PY, bump_app_py),
|
||||||
(GITEA_WORKFLOW, bump_workflow),
|
(GITEA_WORKFLOW, bump_workflow),
|
||||||
(CHANGELOG, bump_changelog)
|
(CHANGELOG, bump_changelog),
|
||||||
|
(DEBIAN_CHANGELOG, bump_debian_changelog)
|
||||||
]
|
]
|
||||||
|
|
||||||
updated = []
|
updated = []
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ Current translation status:
|
|||||||
|
|
||||||
| Locale | Progress | Translated |
|
| Locale | Progress | Translated |
|
||||||
| :----- | -------: | ---------: |
|
| :----- | -------: | ---------: |
|
||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 338 |
|
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 341 |
|
||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 338 |
|
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 341 |
|
||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 338 of 338 |
|
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 341 of 341 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
| Локаль | Прогресс | Переведено |
|
| Локаль | Прогресс | Переведено |
|
||||||
| :----- | -------: | ---------: |
|
| :----- | -------: | ---------: |
|
||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 338 |
|
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 341 |
|
||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 338 |
|
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 341 |
|
||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 338 из 338 |
|
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 341 из 341 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from portprotonqt.cli import parse_args
|
|||||||
|
|
||||||
__app_id__ = "ru.linux_gaming.PortProtonQt"
|
__app_id__ = "ru.linux_gaming.PortProtonQt"
|
||||||
__app_name__ = "PortProtonQt"
|
__app_name__ = "PortProtonQt"
|
||||||
__app_version__ = "0.1.8"
|
__app_version__ = "0.1.9"
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -183,23 +183,39 @@ def get_portproton_start_command():
|
|||||||
if not portproton_path:
|
if not portproton_path:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Check if flatpak command exists before trying to run it
|
||||||
try:
|
try:
|
||||||
result = subprocess.run(
|
subprocess.run(
|
||||||
["flatpak", "list"],
|
["flatpak", "--version"],
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
timeout=10
|
timeout=5
|
||||||
)
|
)
|
||||||
if "ru.linux_gaming.PortProton" in result.stdout:
|
flatpak_available = True
|
||||||
logger.info("Detected Flatpak installation")
|
except FileNotFoundError:
|
||||||
return ["flatpak", "run", "ru.linux_gaming.PortProton"]
|
flatpak_available = False
|
||||||
except subprocess.TimeoutExpired:
|
except Exception:
|
||||||
logger.warning("Flatpak list command timed out")
|
flatpak_available = False
|
||||||
return None
|
|
||||||
except Exception as e:
|
if flatpak_available:
|
||||||
logger.warning(f"Error checking flatpak list: {e}")
|
try:
|
||||||
pass
|
result = subprocess.run(
|
||||||
|
["flatpak", "list"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
if "ru.linux_gaming.PortProton" in result.stdout:
|
||||||
|
logger.info("Detected Flatpak installation")
|
||||||
|
return ["flatpak", "run", "ru.linux_gaming.PortProton"]
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.warning("Flatpak list command timed out")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error checking flatpak list: {e}")
|
||||||
|
pass
|
||||||
|
|
||||||
start_sh_path = os.path.join(portproton_path, "data", "scripts", "start.sh")
|
start_sh_path = os.path.join(portproton_path, "data", "scripts", "start.sh")
|
||||||
if os.path.exists(start_sh_path):
|
if os.path.exists(start_sh_path):
|
||||||
|
|||||||
@@ -1035,7 +1035,15 @@ Icon={icon_path}
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if os.path.isfile(new_cover_path):
|
# Check if new_cover_path is a URL by checking for common image extensions
|
||||||
|
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp')
|
||||||
|
has_image_extension = any(new_cover_path.lower().endswith(ext) for ext in image_extensions)
|
||||||
|
|
||||||
|
# Consider it a URL if it has image extension and is not a local file
|
||||||
|
is_url = has_image_extension and not os.path.isfile(new_cover_path)
|
||||||
|
|
||||||
|
# Use the downloaded file path if we have a URL and the file was downloaded, otherwise use the local file
|
||||||
|
if os.path.isfile(new_cover_path) or (is_url and dialog.last_cover_path and os.path.isfile(dialog.last_cover_path)):
|
||||||
exe_name = os.path.splitext(os.path.basename(new_exe_path))[0]
|
exe_name = os.path.splitext(os.path.basename(new_exe_path))[0]
|
||||||
xdg_data_home = os.getenv(
|
xdg_data_home = os.getenv(
|
||||||
"XDG_DATA_HOME",
|
"XDG_DATA_HOME",
|
||||||
@@ -1043,16 +1051,25 @@ Icon={icon_path}
|
|||||||
)
|
)
|
||||||
custom_folder = os.path.join(xdg_data_home, "PortProtonQt", "custom_data", exe_name)
|
custom_folder = os.path.join(xdg_data_home, "PortProtonQt", "custom_data", exe_name)
|
||||||
os.makedirs(custom_folder, exist_ok=True)
|
os.makedirs(custom_folder, exist_ok=True)
|
||||||
ext = os.path.splitext(new_cover_path)[1].lower()
|
|
||||||
|
# Use the actual cover file path (either from URL download or local file)
|
||||||
|
cover_to_copy = dialog.last_cover_path if is_url and dialog.last_cover_path and os.path.isfile(dialog.last_cover_path) else new_cover_path
|
||||||
|
ext = os.path.splitext(cover_to_copy)[1].lower()
|
||||||
if ext in [".png", ".jpg", ".jpeg", ".bmp"]:
|
if ext in [".png", ".jpg", ".jpeg", ".bmp"]:
|
||||||
try:
|
try:
|
||||||
shutil.copyfile(new_cover_path, os.path.join(custom_folder, f"cover{ext}"))
|
shutil.copyfile(cover_to_copy, os.path.join(custom_folder, f"cover{ext}"))
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.signals.show_warning_dialog.emit(
|
self.signals.show_warning_dialog.emit(
|
||||||
_("Error"),
|
_("Error"),
|
||||||
_("Failed to copy cover image: {error}").format(error=str(e))
|
_("Failed to copy cover image: {error}").format(error=str(e))
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
self.signals.show_warning_dialog.emit(
|
||||||
|
_("Error"),
|
||||||
|
_("Unsupported image format: {extension}").format(extension=ext)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
def add_to_steam(self, game_name, exec_line, cover_path):
|
def add_to_steam(self, game_name, exec_line, cover_path):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from PySide6.QtWidgets import (
|
|||||||
from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer, QThreadPool, QRunnable, Slot, QProcess, QProcessEnvironment
|
from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase, QTimer, QThreadPool, QRunnable, Slot, QProcess, QProcessEnvironment
|
||||||
from icoextract import IconExtractor, IconExtractorError
|
from icoextract import IconExtractor, IconExtractorError
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from portprotonqt.config_utils import get_portproton_location, read_favorite_folders, read_theme_from_config
|
from portprotonqt.config_utils import get_portproton_location, get_portproton_start_command, read_favorite_folders, read_theme_from_config
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
@@ -906,6 +906,7 @@ class AddGameDialog(QDialog):
|
|||||||
|
|
||||||
self.coverEdit = CustomLineEdit(self, theme=self.theme)
|
self.coverEdit = CustomLineEdit(self, theme=self.theme)
|
||||||
self.coverEdit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
|
self.coverEdit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
|
||||||
|
self.coverEdit.setPlaceholderText(_("Enter local path or URL for cover image"))
|
||||||
if cover_path:
|
if cover_path:
|
||||||
self.coverEdit.setText(cover_path)
|
self.coverEdit.setText(cover_path)
|
||||||
|
|
||||||
@@ -949,7 +950,12 @@ class AddGameDialog(QDialog):
|
|||||||
# Подключение сигналов
|
# Подключение сигналов
|
||||||
self.select_button.clicked.connect(self.accept)
|
self.select_button.clicked.connect(self.accept)
|
||||||
self.cancel_button.clicked.connect(self.reject)
|
self.cancel_button.clicked.connect(self.reject)
|
||||||
self.coverEdit.textChanged.connect(self.updatePreview)
|
# Set up a timer for debounced cover preview updates
|
||||||
|
self.cover_preview_timer = QTimer(self)
|
||||||
|
self.cover_preview_timer.setSingleShot(True)
|
||||||
|
self.cover_preview_timer.timeout.connect(self.updatePreview)
|
||||||
|
|
||||||
|
self.coverEdit.textChanged.connect(self.onCoverTextChanged)
|
||||||
self.exeEdit.textChanged.connect(self.updatePreview)
|
self.exeEdit.textChanged.connect(self.updatePreview)
|
||||||
|
|
||||||
# Установка одинаковой ширины для кнопок и полей ввода
|
# Установка одинаковой ширины для кнопок и полей ввода
|
||||||
@@ -1083,33 +1089,51 @@ class AddGameDialog(QDialog):
|
|||||||
|
|
||||||
def handleDownloadedCover(self, file_path):
|
def handleDownloadedCover(self, file_path):
|
||||||
"""Handle the downloaded cover image and update the preview."""
|
"""Handle the downloaded cover image and update the preview."""
|
||||||
if file_path and os.path.isfile(file_path):
|
# Check if the dialog or widget has been destroyed before updating
|
||||||
self.last_cover_path = file_path
|
if not hasattr(self, 'coverPreview') or self.coverPreview is None:
|
||||||
pixmap = QPixmap(file_path)
|
return
|
||||||
if not pixmap.isNull():
|
|
||||||
self.coverPreview.setPixmap(pixmap.scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio))
|
try:
|
||||||
|
if file_path and os.path.isfile(file_path):
|
||||||
|
self.last_cover_path = file_path
|
||||||
|
pixmap = QPixmap(file_path)
|
||||||
|
if not pixmap.isNull():
|
||||||
|
self.coverPreview.setPixmap(pixmap.scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio))
|
||||||
|
else:
|
||||||
|
self.coverPreview.setText(_("Invalid image"))
|
||||||
else:
|
else:
|
||||||
self.coverPreview.setText(_("Invalid image"))
|
self.coverPreview.setText(_("Failed to download cover"))
|
||||||
else:
|
logger.warning(f"Failed to download cover to {file_path}")
|
||||||
self.coverPreview.setText(_("Failed to download cover"))
|
except RuntimeError:
|
||||||
logger.warning(f"Failed to download cover to {file_path}")
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
|
def onCoverTextChanged(self):
|
||||||
|
"""Handle cover text changes with debounce."""
|
||||||
|
# Restart the timer to delay the preview update
|
||||||
|
self.cover_preview_timer.start(500) # 500ms delay
|
||||||
|
|
||||||
def updatePreview(self):
|
def updatePreview(self):
|
||||||
"""Update the cover preview image."""
|
"""Update the cover preview image."""
|
||||||
cover_path = self.coverEdit.text().strip()
|
cover_path = self.coverEdit.text().strip()
|
||||||
exe_path = self.exeEdit.text().strip()
|
exe_path = self.exeEdit.text().strip()
|
||||||
|
|
||||||
# Check if cover_path is a URL
|
# Check if cover_path is a URL by checking for common image extensions
|
||||||
url_pattern = r'^https?://[^\s/$.?#].[^\s]*$'
|
image_extensions = ('.jpg', '.jpeg', '.png', '.bmp', '.gif', '.webp')
|
||||||
if re.match(url_pattern, cover_path):
|
has_image_extension = any(cover_path.lower().endswith(ext) for ext in image_extensions)
|
||||||
|
|
||||||
|
# Consider it a URL if it has image extension and is not a local file
|
||||||
|
if has_image_extension and not os.path.isfile(cover_path):
|
||||||
# Create a temporary file for the downloaded image
|
# Create a temporary file for the downloaded image
|
||||||
fd, local_path = tempfile.mkstemp(suffix=".png")
|
fd, local_path = tempfile.mkstemp(suffix=".png")
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
os.unlink(local_path)
|
os.unlink(local_path)
|
||||||
|
|
||||||
# Start asynchronous download
|
# Start asynchronous download
|
||||||
|
# Add protocol if not present
|
||||||
|
download_url = cover_path if cover_path.startswith(('http://', 'https://')) else f'https://{cover_path}'
|
||||||
self.downloader.download_async(
|
self.downloader.download_async(
|
||||||
url=cover_path,
|
url=download_url,
|
||||||
local_path=local_path,
|
local_path=local_path,
|
||||||
timeout=10,
|
timeout=10,
|
||||||
callback=self.handleDownloadedCover
|
callback=self.handleDownloadedCover
|
||||||
@@ -1701,8 +1725,10 @@ class ExeSettingsDialog(QDialog):
|
|||||||
if self.portproton_path is None:
|
if self.portproton_path is None:
|
||||||
logger.error("PortProton location not found")
|
logger.error("PortProton location not found")
|
||||||
return
|
return
|
||||||
base_path = os.path.join(self.portproton_path, "data")
|
self.start_sh = get_portproton_start_command()
|
||||||
self.start_sh = [os.path.join(base_path, "scripts", "start.sh")]
|
if self.start_sh is None:
|
||||||
|
logger.error("PortProton start command not found")
|
||||||
|
return
|
||||||
|
|
||||||
self.dist_options = []
|
self.dist_options = []
|
||||||
self.prefix_options = []
|
self.prefix_options = []
|
||||||
@@ -1776,9 +1802,9 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.load_current_settings()
|
self.load_current_settings()
|
||||||
|
|
||||||
def _get_process_args(self, subcommand_args):
|
def _get_process_args(self, subcommand_args):
|
||||||
"""Get the full arguments for QProcess.start, handling flatpak separator."""
|
"""Get the full arguments for QProcess.start, handling flatpak format."""
|
||||||
if self.start_sh[0] == "flatpak":
|
if self.start_sh and self.start_sh[0] == "flatpak":
|
||||||
return self.start_sh[1:] + ["--"] + subcommand_args
|
return self.start_sh + subcommand_args
|
||||||
else:
|
else:
|
||||||
return self.start_sh + subcommand_args
|
return self.start_sh + subcommand_args
|
||||||
|
|
||||||
@@ -1814,7 +1840,7 @@ class ExeSettingsDialog(QDialog):
|
|||||||
# Connect tab change to update description hint
|
# Connect tab change to update description hint
|
||||||
self.tab_widget.currentChanged.connect(self.on_table_selection_changed)
|
self.tab_widget.currentChanged.connect(self.on_table_selection_changed)
|
||||||
|
|
||||||
# Main settings table
|
# Main settings table with preloader
|
||||||
self.settings_table = QTableWidget()
|
self.settings_table = QTableWidget()
|
||||||
self.settings_table.setAlternatingRowColors(True)
|
self.settings_table.setAlternatingRowColors(True)
|
||||||
self.settings_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
self.settings_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
@@ -1829,11 +1855,30 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.settings_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
self.settings_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
||||||
self.settings_table.setTextElideMode(Qt.TextElideMode.ElideNone)
|
self.settings_table.setTextElideMode(Qt.TextElideMode.ElideNone)
|
||||||
self.settings_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE)
|
self.settings_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE)
|
||||||
self.main_tab_layout.addWidget(self.settings_table)
|
|
||||||
|
# Create preloader for main settings table
|
||||||
|
self.settings_preloader = Preloader()
|
||||||
|
settings_preloader_container = QWidget()
|
||||||
|
settings_preloader_layout = QVBoxLayout(settings_preloader_container)
|
||||||
|
settings_preloader_layout.addStretch()
|
||||||
|
settings_preloader_hlayout = QHBoxLayout()
|
||||||
|
settings_preloader_hlayout.addStretch()
|
||||||
|
settings_preloader_hlayout.addWidget(self.settings_preloader)
|
||||||
|
settings_preloader_hlayout.addStretch()
|
||||||
|
settings_preloader_layout.addLayout(settings_preloader_hlayout)
|
||||||
|
settings_preloader_layout.addStretch()
|
||||||
|
settings_preloader_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
settings_preloader_layout.setSpacing(0)
|
||||||
|
|
||||||
|
# Create stacked widget for main settings
|
||||||
|
self.settings_container = QStackedWidget()
|
||||||
|
self.settings_container.addWidget(settings_preloader_container) # Index 0: preloader
|
||||||
|
self.settings_container.addWidget(self.settings_table) # Index 1: table
|
||||||
|
self.main_tab_layout.addWidget(self.settings_container)
|
||||||
# Connect selection changed signal for the main table
|
# Connect selection changed signal for the main table
|
||||||
self.settings_table.currentCellChanged.connect(self.on_table_selection_changed)
|
self.settings_table.currentCellChanged.connect(self.on_table_selection_changed)
|
||||||
|
|
||||||
# Advanced settings table
|
# Advanced settings table with preloader
|
||||||
self.advanced_table = QTableWidget()
|
self.advanced_table = QTableWidget()
|
||||||
self.advanced_table.setAlternatingRowColors(True)
|
self.advanced_table.setAlternatingRowColors(True)
|
||||||
self.advanced_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
self.advanced_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
@@ -1850,7 +1895,26 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.advanced_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
self.advanced_table.verticalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)
|
||||||
self.advanced_table.setTextElideMode(Qt.TextElideMode.ElideNone)
|
self.advanced_table.setTextElideMode(Qt.TextElideMode.ElideNone)
|
||||||
self.advanced_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE)
|
self.advanced_table.setStyleSheet(self.theme.WINETRICKS_TABBLE_STYLE)
|
||||||
self.advanced_tab_layout.addWidget(self.advanced_table)
|
|
||||||
|
# Create preloader for advanced settings table
|
||||||
|
self.advanced_preloader = Preloader()
|
||||||
|
advanced_preloader_container = QWidget()
|
||||||
|
advanced_preloader_layout = QVBoxLayout(advanced_preloader_container)
|
||||||
|
advanced_preloader_layout.addStretch()
|
||||||
|
advanced_preloader_hlayout = QHBoxLayout()
|
||||||
|
advanced_preloader_hlayout.addStretch()
|
||||||
|
advanced_preloader_hlayout.addWidget(self.advanced_preloader)
|
||||||
|
advanced_preloader_hlayout.addStretch()
|
||||||
|
advanced_preloader_layout.addLayout(advanced_preloader_hlayout)
|
||||||
|
advanced_preloader_layout.addStretch()
|
||||||
|
advanced_preloader_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
advanced_preloader_layout.setSpacing(0)
|
||||||
|
|
||||||
|
# Create stacked widget for advanced settings
|
||||||
|
self.advanced_container = QStackedWidget()
|
||||||
|
self.advanced_container.addWidget(advanced_preloader_container) # Index 0: preloader
|
||||||
|
self.advanced_container.addWidget(self.advanced_table) # Index 1: table
|
||||||
|
self.advanced_tab_layout.addWidget(self.advanced_container)
|
||||||
# Connect selection changed signal for the advanced table
|
# Connect selection changed signal for the advanced table
|
||||||
self.advanced_table.currentCellChanged.connect(self.on_table_selection_changed)
|
self.advanced_table.currentCellChanged.connect(self.on_table_selection_changed)
|
||||||
|
|
||||||
@@ -1888,9 +1952,14 @@ class ExeSettingsDialog(QDialog):
|
|||||||
|
|
||||||
def load_current_settings(self):
|
def load_current_settings(self):
|
||||||
"""Load available toggles first, then current settings."""
|
"""Load available toggles first, then current settings."""
|
||||||
|
# Show preloaders initially
|
||||||
|
self.settings_container.setCurrentIndex(0) # Show preloader for main settings
|
||||||
|
self.advanced_container.setCurrentIndex(0) # Show preloader for advanced settings
|
||||||
|
|
||||||
process = QProcess(self)
|
process = QProcess(self)
|
||||||
process.finished.connect(self.on_list_db_finished)
|
process.finished.connect(self.on_list_db_finished)
|
||||||
process.start(self.start_sh[0], ["cli", "--list-db"])
|
args = self._get_process_args(["cli", "--list-db"])
|
||||||
|
process.start(args[0], args[1:])
|
||||||
|
|
||||||
def on_list_db_finished(self, exit_code, exit_status):
|
def on_list_db_finished(self, exit_code, exit_status):
|
||||||
"""Handle --list-db output and extract available keys and system info."""
|
"""Handle --list-db output and extract available keys and system info."""
|
||||||
@@ -1912,6 +1981,9 @@ class ExeSettingsDialog(QDialog):
|
|||||||
if re.match(r'^[A-Z_0-9]+=[^=]+$', line_stripped) and not line_stripped.startswith('PW_'):
|
if re.match(r'^[A-Z_0-9]+=[^=]+$', line_stripped) and not line_stripped.startswith('PW_'):
|
||||||
# System info
|
# System info
|
||||||
k, v = line_stripped.split('=', 1)
|
k, v = line_stripped.split('=', 1)
|
||||||
|
# Remove surrounding quotes from the value if present
|
||||||
|
if v.startswith('"') and v.endswith('"') and len(v) >= 2:
|
||||||
|
v = v[1:-1]
|
||||||
if k.startswith('NUMA_NODE_'):
|
if k.startswith('NUMA_NODE_'):
|
||||||
node_id = k[10:]
|
node_id = k[10:]
|
||||||
self.numa_nodes[node_id] = v
|
self.numa_nodes[node_id] = v
|
||||||
@@ -1937,7 +2009,8 @@ class ExeSettingsDialog(QDialog):
|
|||||||
# Load current settings
|
# Load current settings
|
||||||
process = QProcess(self)
|
process = QProcess(self)
|
||||||
process.finished.connect(self.on_show_ppdb_finished)
|
process.finished.connect(self.on_show_ppdb_finished)
|
||||||
process.start(self.start_sh[0], ["cli", "--show-ppdb", f"{self.exe_path}"])
|
args = self._get_process_args(["cli", "--show-ppdb", f"{self.exe_path}"])
|
||||||
|
process.start(args[0], args[1:])
|
||||||
|
|
||||||
def on_show_ppdb_finished(self, exit_code, exit_status):
|
def on_show_ppdb_finished(self, exit_code, exit_status):
|
||||||
"""Handle --show-ppdb output."""
|
"""Handle --show-ppdb output."""
|
||||||
@@ -1957,6 +2030,9 @@ class ExeSettingsDialog(QDialog):
|
|||||||
try:
|
try:
|
||||||
key, val = line_stripped.split('=', 1)
|
key, val = line_stripped.split('=', 1)
|
||||||
if key in self.toggle_settings or key in ADVANCED_SETTING_KEYS:
|
if key in self.toggle_settings or key in ADVANCED_SETTING_KEYS:
|
||||||
|
# Remove surrounding quotes from the value if present
|
||||||
|
if val.startswith('"') and val.endswith('"') and len(val) >= 2:
|
||||||
|
val = val[1:-1]
|
||||||
self.current_settings[key] = val
|
self.current_settings[key] = val
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
@@ -1977,6 +2053,10 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.populate_table()
|
self.populate_table()
|
||||||
self.populate_advanced()
|
self.populate_advanced()
|
||||||
|
|
||||||
|
# Show the loaded content and hide preloaders
|
||||||
|
self.settings_container.setCurrentIndex(1) # Show main settings table
|
||||||
|
self.advanced_container.setCurrentIndex(1) # Show advanced settings table
|
||||||
|
|
||||||
def populate_table(self):
|
def populate_table(self):
|
||||||
"""Populate the table with settings that are available in both lists."""
|
"""Populate the table with settings that are available in both lists."""
|
||||||
self.settings_table.setRowCount(0)
|
self.settings_table.setRowCount(0)
|
||||||
@@ -2285,8 +2365,9 @@ class ExeSettingsDialog(QDialog):
|
|||||||
|
|
||||||
process = QProcess(self)
|
process = QProcess(self)
|
||||||
process.finished.connect(self.on_edit_db_finished)
|
process.finished.connect(self.on_edit_db_finished)
|
||||||
args = ["cli", "--edit-db", self.exe_path] + changes
|
process_args = ["cli", "--edit-db", self.exe_path] + changes
|
||||||
process.start(self.start_sh[0], args)
|
args = self._get_process_args(process_args)
|
||||||
|
process.start(args[0], args[1:])
|
||||||
self.apply_button.setEnabled(False)
|
self.apply_button.setEnabled(False)
|
||||||
|
|
||||||
def on_edit_db_finished(self, exit_code, exit_status):
|
def on_edit_db_finished(self, exit_code, exit_status):
|
||||||
|
|||||||
@@ -200,13 +200,27 @@ class GameCard(QFrame):
|
|||||||
self.update_cover_pixmap()
|
self.update_cover_pixmap()
|
||||||
|
|
||||||
def update_cover_pixmap(self):
|
def update_cover_pixmap(self):
|
||||||
|
# Check if the coverLabel still exists before trying to update it
|
||||||
|
# This prevents the "Internal C++ object already deleted" error when
|
||||||
|
# the widget has been destroyed but the async callback still executes
|
||||||
|
if not hasattr(self, 'coverLabel') or self.coverLabel is None:
|
||||||
|
return
|
||||||
|
|
||||||
if self.base_pixmap and not self.base_pixmap.isNull():
|
if self.base_pixmap and not self.base_pixmap.isNull():
|
||||||
scaled_width = int(self.base_card_width * self._scale)
|
scaled_width = int(self.base_card_width * self._scale)
|
||||||
scaled_pixmap = self.base_pixmap.scaled(scaled_width, int(scaled_width * 1.5), Qt.AspectRatioMode.KeepAspectRatioByExpanding, Qt.TransformationMode.SmoothTransformation)
|
scaled_pixmap = self.base_pixmap.scaled(scaled_width, int(scaled_width * 1.5), Qt.AspectRatioMode.KeepAspectRatioByExpanding, Qt.TransformationMode.SmoothTransformation)
|
||||||
rounded_pixmap = round_corners(scaled_pixmap, int(15 * self._scale))
|
rounded_pixmap = round_corners(scaled_pixmap, int(15 * self._scale))
|
||||||
self.coverLabel.setPixmap(rounded_pixmap)
|
try:
|
||||||
|
self.coverLabel.setPixmap(rounded_pixmap)
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted between the check and the call
|
||||||
|
pass
|
||||||
|
|
||||||
def _position_badges(self, current_width):
|
def _position_badges(self, current_width):
|
||||||
|
# Check if the card has been destroyed before updating
|
||||||
|
if not hasattr(self, 'coverLabel') or self.coverLabel is None:
|
||||||
|
return
|
||||||
|
|
||||||
right_margin = int(8 * self._scale)
|
right_margin = int(8 * self._scale)
|
||||||
badge_spacing = int(current_width * 0.02)
|
badge_spacing = int(current_width * 0.02)
|
||||||
top_y = int(10 * self._scale)
|
top_y = int(10 * self._scale)
|
||||||
@@ -225,16 +239,28 @@ class GameCard(QFrame):
|
|||||||
if is_visible:
|
if is_visible:
|
||||||
badge_x = current_width - badge_width - right_margin
|
badge_x = current_width - badge_width - right_margin
|
||||||
badge_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
|
badge_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
|
||||||
badge.move(int(badge_x), int(badge_y))
|
try:
|
||||||
badge_y_positions.append(badge_y + badge.height())
|
badge.move(int(badge_x), int(badge_y))
|
||||||
|
badge_y_positions.append(badge_y + badge.height())
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
self.anticheatLabel.raise_()
|
try:
|
||||||
self.protondbLabel.raise_()
|
self.anticheatLabel.raise_()
|
||||||
self.portprotonLabel.raise_()
|
self.protondbLabel.raise_()
|
||||||
self.egsLabel.raise_()
|
self.portprotonLabel.raise_()
|
||||||
self.steamLabel.raise_()
|
self.egsLabel.raise_()
|
||||||
|
self.steamLabel.raise_()
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
def update_scale(self):
|
def update_scale(self):
|
||||||
|
# Check if the card has been destroyed before updating
|
||||||
|
if not hasattr(self, 'coverLabel') or self.coverLabel is None:
|
||||||
|
return
|
||||||
|
|
||||||
scaled_width = int(self.base_card_width * self._scale)
|
scaled_width = int(self.base_card_width * self._scale)
|
||||||
scaled_height = int(self.base_card_width * 1.8 * self._scale)
|
scaled_height = int(self.base_card_width * 1.8 * self._scale)
|
||||||
scaled_extra = int(self.base_extra_margin * self._scale)
|
scaled_extra = int(self.base_extra_margin * self._scale)
|
||||||
@@ -255,33 +281,53 @@ class GameCard(QFrame):
|
|||||||
icon_space = int(scaled_width * 0.012)
|
icon_space = int(scaled_width * 0.012)
|
||||||
for label in [self.steamLabel, self.egsLabel, self.portprotonLabel, self.protondbLabel, self.anticheatLabel]:
|
for label in [self.steamLabel, self.egsLabel, self.portprotonLabel, self.protondbLabel, self.anticheatLabel]:
|
||||||
if label is not None:
|
if label is not None:
|
||||||
label.setFixedWidth(badge_width)
|
try:
|
||||||
label.setIconSize(icon_size, icon_space)
|
label.setFixedWidth(badge_width)
|
||||||
label.setCardWidth(scaled_width)
|
label.setIconSize(icon_size, icon_space)
|
||||||
|
label.setCardWidth(scaled_width)
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
self._position_badges(scaled_width)
|
self._position_badges(scaled_width)
|
||||||
|
|
||||||
if self.base_font_size is not None:
|
if self.base_font_size is not None:
|
||||||
font = self.nameLabel.font()
|
try:
|
||||||
new_font_size = self.base_font_size * self._scale
|
font = self.nameLabel.font()
|
||||||
if new_font_size > 0:
|
new_font_size = self.base_font_size * self._scale
|
||||||
font.setPointSizeF(new_font_size)
|
if new_font_size > 0:
|
||||||
self.nameLabel.setFont(font)
|
font.setPointSizeF(new_font_size)
|
||||||
|
self.nameLabel.setFont(font)
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
self.shadow.setBlurRadius(int(20 * self._scale))
|
try:
|
||||||
|
self.shadow.setBlurRadius(int(20 * self._scale))
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
self.updateGeometry()
|
try:
|
||||||
self.update()
|
self.updateGeometry()
|
||||||
|
self.update()
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
# Ensure parent layout is updated safely
|
# Ensure parent layout is updated safely
|
||||||
parent = self.parentWidget()
|
try:
|
||||||
if parent:
|
parent = self.parentWidget()
|
||||||
layout = parent.layout()
|
if parent:
|
||||||
if layout:
|
layout = parent.layout()
|
||||||
layout.invalidate()
|
if layout:
|
||||||
layout.activate()
|
layout.invalidate()
|
||||||
layout.update()
|
layout.activate()
|
||||||
parent.updateGeometry()
|
layout.update()
|
||||||
|
parent.updateGeometry()
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
def update_card_size(self, new_width: int):
|
def update_card_size(self, new_width: int):
|
||||||
self.base_card_width = new_width
|
self.base_card_width = new_width
|
||||||
@@ -289,6 +335,10 @@ class GameCard(QFrame):
|
|||||||
self.update_scale()
|
self.update_scale()
|
||||||
|
|
||||||
def update_badge_visibility(self, display_filter: str):
|
def update_badge_visibility(self, display_filter: str):
|
||||||
|
# Check if the card has been destroyed before updating
|
||||||
|
if not hasattr(self, 'coverLabel') or self.coverLabel is None:
|
||||||
|
return
|
||||||
|
|
||||||
self.display_filter = display_filter
|
self.display_filter = display_filter
|
||||||
self.steam_visible = (str(self.game_source).lower() == "steam" and self.display_filter in ("all", "favorites"))
|
self.steam_visible = (str(self.game_source).lower() == "steam" and self.display_filter in ("all", "favorites"))
|
||||||
self.egs_visible = (str(self.game_source).lower() == "epic" and self.display_filter in ("all", "favorites"))
|
self.egs_visible = (str(self.game_source).lower() == "epic" and self.display_filter in ("all", "favorites"))
|
||||||
@@ -296,11 +346,15 @@ class GameCard(QFrame):
|
|||||||
protondb_visible = bool(self.getProtonDBText(self.protondb_tier))
|
protondb_visible = bool(self.getProtonDBText(self.protondb_tier))
|
||||||
anticheat_visible = bool(self.getAntiCheatText(self.anticheat_status))
|
anticheat_visible = bool(self.getAntiCheatText(self.anticheat_status))
|
||||||
|
|
||||||
self.steamLabel.setVisible(self.steam_visible)
|
try:
|
||||||
self.egsLabel.setVisible(self.egs_visible)
|
self.steamLabel.setVisible(self.steam_visible)
|
||||||
self.portprotonLabel.setVisible(self.portproton_visible)
|
self.egsLabel.setVisible(self.egs_visible)
|
||||||
self.protondbLabel.setVisible(protondb_visible)
|
self.portprotonLabel.setVisible(self.portproton_visible)
|
||||||
self.anticheatLabel.setVisible(anticheat_visible)
|
self.protondbLabel.setVisible(protondb_visible)
|
||||||
|
self.anticheatLabel.setVisible(anticheat_visible)
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
return
|
||||||
|
|
||||||
scaled_width = int(self.base_card_width * self._scale)
|
scaled_width = int(self.base_card_width * self._scale)
|
||||||
self._position_badges(scaled_width)
|
self._position_badges(scaled_width)
|
||||||
@@ -395,21 +449,33 @@ class GameCard(QFrame):
|
|||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
||||||
|
|
||||||
def update_favorite_icon(self):
|
def update_favorite_icon(self):
|
||||||
if self.is_favorite:
|
# Check if the card has been destroyed before updating
|
||||||
self.favoriteLabel.setText("★")
|
if not hasattr(self, 'coverLabel') or self.coverLabel is None:
|
||||||
else:
|
return
|
||||||
self.favoriteLabel.setText("☆")
|
|
||||||
self.favoriteLabel.setStyleSheet(self.theme.FAVORITE_LABEL_STYLE)
|
|
||||||
|
|
||||||
parent = self.parent()
|
try:
|
||||||
while parent:
|
if self.is_favorite:
|
||||||
if hasattr(parent, 'game_library_manager'):
|
self.favoriteLabel.setText("★")
|
||||||
# Access using getattr with default to avoid Ruff B009 warning
|
else:
|
||||||
manager = getattr(parent, 'game_library_manager', None)
|
self.favoriteLabel.setText("☆")
|
||||||
if manager is not None:
|
self.favoriteLabel.setStyleSheet(self.theme.FAVORITE_LABEL_STYLE)
|
||||||
QTimer.singleShot(0, manager.update_game_grid)
|
except RuntimeError:
|
||||||
break
|
# Handle the case where the Qt object was deleted
|
||||||
parent = parent.parent()
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
parent = self.parent()
|
||||||
|
while parent:
|
||||||
|
if hasattr(parent, 'game_library_manager'):
|
||||||
|
# Access using getattr with default to avoid Ruff B009 warning
|
||||||
|
manager = getattr(parent, 'game_library_manager', None)
|
||||||
|
if manager is not None:
|
||||||
|
QTimer.singleShot(0, manager.update_game_grid)
|
||||||
|
break
|
||||||
|
parent = parent.parent()
|
||||||
|
except RuntimeError:
|
||||||
|
# Handle the case where the Qt object was deleted
|
||||||
|
pass
|
||||||
|
|
||||||
def toggle_favorite(self):
|
def toggle_favorite(self):
|
||||||
favorites = read_favorites()
|
favorites = read_favorites()
|
||||||
|
|||||||
@@ -167,12 +167,18 @@ class GameLibraryManager:
|
|||||||
|
|
||||||
if is_focused:
|
if is_focused:
|
||||||
if self.main_window.current_hovered_card and self.main_window.current_hovered_card != card:
|
if self.main_window.current_hovered_card and self.main_window.current_hovered_card != card:
|
||||||
self.main_window.current_hovered_card._hovered = False
|
try:
|
||||||
self.main_window.current_hovered_card.leaveEvent(None)
|
self.main_window.current_hovered_card._hovered = False
|
||||||
|
self.main_window.current_hovered_card.leaveEvent(None)
|
||||||
|
except RuntimeError:
|
||||||
|
pass # Card already deleted
|
||||||
self.main_window.current_hovered_card = None
|
self.main_window.current_hovered_card = None
|
||||||
if self.main_window.current_focused_card and self.main_window.current_focused_card != card:
|
if self.main_window.current_focused_card and self.main_window.current_focused_card != card:
|
||||||
self.main_window.current_focused_card._focused = False
|
try:
|
||||||
self.main_window.current_focused_card.clearFocus()
|
self.main_window.current_focused_card._focused = False
|
||||||
|
self.main_window.current_focused_card.clearFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
pass # Card already deleted
|
||||||
self.main_window.current_focused_card = card
|
self.main_window.current_focused_card = card
|
||||||
else:
|
else:
|
||||||
if self.main_window.current_focused_card == card:
|
if self.main_window.current_focused_card == card:
|
||||||
@@ -193,11 +199,19 @@ class GameLibraryManager:
|
|||||||
|
|
||||||
if is_hovered:
|
if is_hovered:
|
||||||
if self.main_window.current_focused_card and self.main_window.current_focused_card != card:
|
if self.main_window.current_focused_card and self.main_window.current_focused_card != card:
|
||||||
self.main_window.current_focused_card._focused = False
|
try:
|
||||||
self.main_window.current_focused_card.clearFocus()
|
if self.main_window.current_focused_card:
|
||||||
|
self.main_window.current_focused_card._focused = False
|
||||||
|
self.main_window.current_focused_card.clearFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
pass # Card already deleted
|
||||||
if self.main_window.current_hovered_card and self.main_window.current_hovered_card != card:
|
if self.main_window.current_hovered_card and self.main_window.current_hovered_card != card:
|
||||||
self.main_window.current_hovered_card._hovered = False
|
try:
|
||||||
self.main_window.current_hovered_card.leaveEvent(None)
|
if self.main_window.current_hovered_card:
|
||||||
|
self.main_window.current_hovered_card._hovered = False
|
||||||
|
self.main_window.current_hovered_card.leaveEvent(None)
|
||||||
|
except RuntimeError:
|
||||||
|
pass # Card already deleted
|
||||||
self.main_window.current_hovered_card = card
|
self.main_window.current_hovered_card = card
|
||||||
else:
|
else:
|
||||||
if self.main_window.current_hovered_card == card:
|
if self.main_window.current_hovered_card == card:
|
||||||
@@ -476,6 +490,7 @@ class GameLibraryManager:
|
|||||||
select_callback=self.main_window.openGameDetailPage,
|
select_callback=self.main_window.openGameDetailPage,
|
||||||
theme=self.theme,
|
theme=self.theme,
|
||||||
card_width=self.card_width,
|
card_width=self.card_width,
|
||||||
|
parent=self.gamesListWidget,
|
||||||
context_menu_manager=self.context_menu_manager
|
context_menu_manager=self.context_menu_manager
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -498,6 +513,11 @@ class GameLibraryManager:
|
|||||||
def _flush_deletions(self):
|
def _flush_deletions(self):
|
||||||
"""Delete pending widgets off the main update cycle."""
|
"""Delete pending widgets off the main update cycle."""
|
||||||
for card in list(self.pending_deletions):
|
for card in list(self.pending_deletions):
|
||||||
|
# Clear any references to this card if it's currently focused/hovered
|
||||||
|
if self.main_window.current_focused_card == card:
|
||||||
|
self.main_window.current_focused_card = None
|
||||||
|
if self.main_window.current_hovered_card == card:
|
||||||
|
self.main_window.current_hovered_card = None
|
||||||
card.deleteLater()
|
card.deleteLater()
|
||||||
self.pending_deletions.remove(card)
|
self.pending_deletions.remove(card)
|
||||||
|
|
||||||
@@ -505,17 +525,25 @@ class GameLibraryManager:
|
|||||||
"""Clears all widgets from the layout."""
|
"""Clears all widgets from the layout."""
|
||||||
if layout is None:
|
if layout is None:
|
||||||
return
|
return
|
||||||
|
# Remove all widgets from the layout and clean up caches
|
||||||
while layout.count():
|
while layout.count():
|
||||||
child = layout.takeAt(0)
|
child = layout.takeAt(0)
|
||||||
if child.widget():
|
if child.widget():
|
||||||
widget = child.widget()
|
widget = child.widget()
|
||||||
|
# Clean up cache if widget exists in it
|
||||||
for key, card in list(self.game_card_cache.items()):
|
for key, card in list(self.game_card_cache.items()):
|
||||||
if card == widget:
|
if card == widget:
|
||||||
del self.game_card_cache[key]
|
del self.game_card_cache[key]
|
||||||
if key in self.pending_images:
|
if key in self.pending_images:
|
||||||
del self.pending_images[key]
|
del self.pending_images[key]
|
||||||
|
break
|
||||||
|
# Always schedule widget for deletion regardless of cache state
|
||||||
widget.deleteLater()
|
widget.deleteLater()
|
||||||
|
|
||||||
|
# Also clear the cache completely if needed (in case layout wasn't in sync)
|
||||||
|
self.game_card_cache.clear()
|
||||||
|
self.pending_images.clear()
|
||||||
|
|
||||||
def set_games(self, games: list[tuple]):
|
def set_games(self, games: list[tuple]):
|
||||||
"""Sets the games list and updates the filtered games."""
|
"""Sets the games list and updates the filtered games."""
|
||||||
self.games = games
|
self.games = games
|
||||||
|
|||||||
@@ -1939,8 +1939,10 @@ class InputManager(QObject):
|
|||||||
active_win.show_next()
|
active_win.show_next()
|
||||||
return True # Consume event to prevent tab switching
|
return True # Consume event to prevent tab switching
|
||||||
|
|
||||||
# Handle tab switching with Left/Right arrow keys when not in GameCard focus or QLineEdit
|
# Handle tab switching with Left/Right arrow keys when not in GameCard focus or QLineEdit or QTableWidget or AutoSizeButton
|
||||||
if key in (Qt.Key.Key_Left, Qt.Key.Key_Right) and not isinstance(focused, GameCard | QLineEdit) and not self.file_explorer:
|
if (key in (Qt.Key.Key_Left, Qt.Key.Key_Right) and
|
||||||
|
not isinstance(focused, GameCard | QLineEdit | QTableWidget | AutoSizeButton) and
|
||||||
|
not self.file_explorer):
|
||||||
idx = self._parent.stackedWidget.currentIndex()
|
idx = self._parent.stackedWidget.currentIndex()
|
||||||
total = len(self._parent.tabButtons)
|
total = len(self._parent.tabButtons)
|
||||||
if key == Qt.Key.Key_Left:
|
if key == Qt.Key.Key_Left:
|
||||||
@@ -1976,12 +1978,6 @@ class InputManager(QObject):
|
|||||||
self.dpad_moved.emit(dpad_code, dpad_value, now)
|
self.dpad_moved.emit(dpad_code, dpad_value, now)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Launch/stop game on detail page
|
|
||||||
if self._parent.currentDetailPage and key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
|
||||||
if self._parent.current_exec_line:
|
|
||||||
self._parent.toggleGame(self._parent.current_exec_line, None)
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Context menu for GameCard
|
# Context menu for GameCard
|
||||||
if isinstance(focused, GameCard):
|
if isinstance(focused, GameCard):
|
||||||
if key == Qt.Key.Key_F10 and modifiers & Qt.KeyboardModifier.ShiftModifier:
|
if key == Qt.Key.Key_F10 and modifiers & Qt.KeyboardModifier.ShiftModifier:
|
||||||
@@ -1991,6 +1987,18 @@ class InputManager(QObject):
|
|||||||
|
|
||||||
# General actions: Activate, Back, Add
|
# General actions: Activate, Back, Add
|
||||||
if key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
if key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
||||||
|
# Special handling for table widgets with checkboxes
|
||||||
|
if isinstance(focused, QTableWidget):
|
||||||
|
current_row = focused.currentRow()
|
||||||
|
current_col = focused.currentColumn()
|
||||||
|
if current_row >= 0 and current_col >= 0:
|
||||||
|
# Check if the cell contains a checkbox
|
||||||
|
item = focused.item(current_row, current_col)
|
||||||
|
if item and (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
||||||
|
# Toggle the checkbox state
|
||||||
|
new_state = Qt.CheckState.Checked if item.checkState() == Qt.CheckState.Unchecked else Qt.CheckState.Unchecked
|
||||||
|
item.setCheckState(new_state)
|
||||||
|
return True
|
||||||
self._parent.activateFocusedWidget()
|
self._parent.activateFocusedWidget()
|
||||||
return True
|
return True
|
||||||
elif key in (Qt.Key.Key_Escape, Qt.Key.Key_Backspace):
|
elif key in (Qt.Key.Key_Escape, Qt.Key.Key_Backspace):
|
||||||
@@ -2345,9 +2353,12 @@ class InputManager(QObject):
|
|||||||
|
|
||||||
self.button_event.emit(event.code, event.value)
|
self.button_event.emit(event.code, event.value)
|
||||||
# Special handling for menu on press only
|
# Special handling for menu on press only
|
||||||
|
# Only handle menu button if our main window is currently active
|
||||||
if (event.value == 1 and event.code in BUTTONS['menu'] and
|
if (event.value == 1 and event.code in BUTTONS['menu'] and
|
||||||
not self._is_gamescope_session and not self.in_guide_combination_attempt):
|
not self._is_gamescope_session and not self.in_guide_combination_attempt):
|
||||||
self.toggle_fullscreen.emit(not self._is_fullscreen)
|
# Check if our main window is the currently active window
|
||||||
|
if self._parent.isActiveWindow():
|
||||||
|
self.toggle_fullscreen.emit(not self._is_fullscreen)
|
||||||
elif event.type == ecodes.EV_ABS:
|
elif event.type == ecodes.EV_ABS:
|
||||||
if event.code in {ecodes.ABS_Z, ecodes.ABS_RZ}:
|
if event.code in {ecodes.ABS_Z, ecodes.ABS_RZ}:
|
||||||
# Trigger handling for UI
|
# Trigger handling for UI
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
|
"POT-Creation-Date: 2025-11-30 13:20+0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: de_DE\n"
|
"Language: de_DE\n"
|
||||||
@@ -217,6 +217,10 @@ msgstr ""
|
|||||||
msgid "Failed to copy cover image: {error}"
|
msgid "Failed to copy cover image: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unsupported image format: {extension}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to add '{game_name}' to Steam: {error}"
|
msgid "Failed to add '{game_name}' to Steam: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -320,6 +324,9 @@ msgstr ""
|
|||||||
msgid "Custom Cover:"
|
msgid "Custom Cover:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enter local path or URL for cover image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -452,6 +459,9 @@ msgstr ""
|
|||||||
msgid "Unknown Game"
|
msgid "Unknown Game"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Starting PortProton..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Library"
|
msgid "Library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -473,6 +483,9 @@ msgstr ""
|
|||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Refresh Grid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Installation already in progress."
|
msgid "Installation already in progress."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -492,9 +505,6 @@ msgstr ""
|
|||||||
msgid "Installation error."
|
msgid "Installation error."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Refresh Grid"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Game library refreshed"
|
msgid "Game library refreshed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
|
"POT-Creation-Date: 2025-11-30 13:20+0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language: es_ES\n"
|
"Language: es_ES\n"
|
||||||
@@ -217,6 +217,10 @@ msgstr ""
|
|||||||
msgid "Failed to copy cover image: {error}"
|
msgid "Failed to copy cover image: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unsupported image format: {extension}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to add '{game_name}' to Steam: {error}"
|
msgid "Failed to add '{game_name}' to Steam: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -320,6 +324,9 @@ msgstr ""
|
|||||||
msgid "Custom Cover:"
|
msgid "Custom Cover:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enter local path or URL for cover image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -452,6 +459,9 @@ msgstr ""
|
|||||||
msgid "Unknown Game"
|
msgid "Unknown Game"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Starting PortProton..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Library"
|
msgid "Library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -473,6 +483,9 @@ msgstr ""
|
|||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Refresh Grid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Installation already in progress."
|
msgid "Installation already in progress."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -492,9 +505,6 @@ msgstr ""
|
|||||||
msgid "Installation error."
|
msgid "Installation error."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Refresh Grid"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Game library refreshed"
|
msgid "Game library refreshed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PortProtonQt 0.1.1\n"
|
"Project-Id-Version: PortProtonQt 0.1.1\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
|
"POT-Creation-Date: 2025-11-30 13:20+0500\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -215,6 +215,10 @@ msgstr ""
|
|||||||
msgid "Failed to copy cover image: {error}"
|
msgid "Failed to copy cover image: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unsupported image format: {extension}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to add '{game_name}' to Steam: {error}"
|
msgid "Failed to add '{game_name}' to Steam: {error}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -318,6 +322,9 @@ msgstr ""
|
|||||||
msgid "Custom Cover:"
|
msgid "Custom Cover:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Enter local path or URL for cover image"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -450,6 +457,9 @@ msgstr ""
|
|||||||
msgid "Unknown Game"
|
msgid "Unknown Game"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Starting PortProton..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Library"
|
msgid "Library"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -471,6 +481,9 @@ msgstr ""
|
|||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Refresh Grid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Installation already in progress."
|
msgid "Installation already in progress."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -490,9 +503,6 @@ msgstr ""
|
|||||||
msgid "Installation error."
|
msgid "Installation error."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Refresh Grid"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Game library refreshed"
|
msgid "Game library refreshed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -9,8 +9,8 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
|
"POT-Creation-Date: 2025-11-30 13:20+0500\n"
|
||||||
"PO-Revision-Date: 2025-11-24 23:47+0500\n"
|
"PO-Revision-Date: 2025-11-30 13:18+0500\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language: ru_RU\n"
|
"Language: ru_RU\n"
|
||||||
"Language-Team: ru_RU <LL@li.org>\n"
|
"Language-Team: ru_RU <LL@li.org>\n"
|
||||||
@@ -222,6 +222,10 @@ msgstr "Не удалось сохранить файл .desktop: {error}"
|
|||||||
msgid "Failed to copy cover image: {error}"
|
msgid "Failed to copy cover image: {error}"
|
||||||
msgstr "Не удалось скопировать обложку: {error}"
|
msgstr "Не удалось скопировать обложку: {error}"
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Unsupported image format: {extension}"
|
||||||
|
msgstr "Неподдерживаемый формат изображения: {extension}"
|
||||||
|
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "Failed to add '{game_name}' to Steam: {error}"
|
msgid "Failed to add '{game_name}' to Steam: {error}"
|
||||||
msgstr "Не удалось добавить '{game_name}' в Steam: {error}"
|
msgstr "Не удалось добавить '{game_name}' в Steam: {error}"
|
||||||
@@ -327,6 +331,9 @@ msgstr "Обзор..."
|
|||||||
msgid "Custom Cover:"
|
msgid "Custom Cover:"
|
||||||
msgstr "Обложка:"
|
msgstr "Обложка:"
|
||||||
|
|
||||||
|
msgid "Enter local path or URL for cover image"
|
||||||
|
msgstr "Введите локальный путь или URL обложки"
|
||||||
|
|
||||||
msgid "Cover Preview:"
|
msgid "Cover Preview:"
|
||||||
msgstr "Предпросмотр обложки:"
|
msgstr "Предпросмотр обложки:"
|
||||||
|
|
||||||
@@ -459,6 +466,9 @@ msgstr "В ожидании"
|
|||||||
msgid "Unknown Game"
|
msgid "Unknown Game"
|
||||||
msgstr "Неизвестная игра"
|
msgstr "Неизвестная игра"
|
||||||
|
|
||||||
|
msgid "Starting PortProton..."
|
||||||
|
msgstr "Инициализация PortProton"
|
||||||
|
|
||||||
msgid "Library"
|
msgid "Library"
|
||||||
msgstr "Библиотека"
|
msgstr "Библиотека"
|
||||||
|
|
||||||
@@ -480,6 +490,9 @@ msgstr "Назад"
|
|||||||
msgid "Fullscreen"
|
msgid "Fullscreen"
|
||||||
msgstr "Полный экран"
|
msgstr "Полный экран"
|
||||||
|
|
||||||
|
msgid "Refresh Grid"
|
||||||
|
msgstr "Обновить"
|
||||||
|
|
||||||
msgid "Installation already in progress."
|
msgid "Installation already in progress."
|
||||||
msgstr "Установка уже выполняется."
|
msgstr "Установка уже выполняется."
|
||||||
|
|
||||||
@@ -499,9 +512,6 @@ msgstr "Установка не удалась."
|
|||||||
msgid "Installation error."
|
msgid "Installation error."
|
||||||
msgstr "Ошибка установки."
|
msgstr "Ошибка установки."
|
||||||
|
|
||||||
msgid "Refresh Grid"
|
|
||||||
msgstr "Обновить"
|
|
||||||
|
|
||||||
msgid "Game library refreshed"
|
msgid "Game library refreshed"
|
||||||
msgstr "Игровая библиотека обновлена"
|
msgstr "Игровая библиотека обновлена"
|
||||||
|
|
||||||
|
|||||||
@@ -780,11 +780,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.pending_games = []
|
self.pending_games = []
|
||||||
self.games = []
|
self.games = []
|
||||||
|
|
||||||
# Show initial progress bar and status message immediately
|
# Show initial progress bar immediately
|
||||||
self.progress_bar.setRange(0, 100) # Set to determinate range
|
self.progress_bar.setRange(0, 100) # Set to determinate range
|
||||||
self.progress_bar.setValue(0)
|
self.progress_bar.setValue(0)
|
||||||
self.progress_bar.setVisible(True)
|
self.progress_bar.setVisible(True)
|
||||||
self.update_status_message.emit(_("Loading games..."), 0)
|
|
||||||
|
|
||||||
# Process events to keep UI responsive
|
# Process events to keep UI responsive
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
@@ -899,8 +898,8 @@ class MainWindow(QMainWindow):
|
|||||||
self.update_status_message.emit
|
self.update_status_message.emit
|
||||||
)
|
)
|
||||||
|
|
||||||
# Run loading with minimal delay to allow UI to be responsive
|
# Run loading immediately to show status without delay
|
||||||
QTimer.singleShot(100, start_loading) # Reduced to 100ms
|
start_loading()
|
||||||
|
|
||||||
def _load_steam_games_async(self, callback: Callable[[list[tuple]], None]):
|
def _load_steam_games_async(self, callback: Callable[[list[tuple]], None]):
|
||||||
steam_games = []
|
steam_games = []
|
||||||
@@ -1200,8 +1199,32 @@ class MainWindow(QMainWindow):
|
|||||||
self.progress_bar.setRange(0, 0) # Indeterminate
|
self.progress_bar.setRange(0, 0) # Indeterminate
|
||||||
self.update_status_message.emit(_("Refreshing game library..."), 0)
|
self.update_status_message.emit(_("Refreshing game library..."), 0)
|
||||||
|
|
||||||
|
# Clear the game card cache and layout to force reload of custom data
|
||||||
|
if hasattr(self, 'game_library_manager') and self.game_library_manager:
|
||||||
|
# Clear the cache to ensure custom data is reloaded
|
||||||
|
self.game_library_manager.game_card_cache.clear()
|
||||||
|
self.game_library_manager.pending_images.clear()
|
||||||
|
# Clear search indices to rebuild with fresh data
|
||||||
|
if hasattr(self.game_library_manager, '_build_search_indices'):
|
||||||
|
# Mark for full rebuild of search indices
|
||||||
|
self.game_library_manager.dirty = True # Force full update
|
||||||
|
|
||||||
|
# Also clear the layout to ensure old widgets are removed
|
||||||
|
if (hasattr(self.game_library_manager, 'gamesListLayout') and
|
||||||
|
self.game_library_manager.gamesListLayout and
|
||||||
|
hasattr(self.game_library_manager, 'gamesListWidget') and
|
||||||
|
self.game_library_manager.gamesListWidget):
|
||||||
|
# Remove all widgets from the layout
|
||||||
|
self.game_library_manager.clear_layout(self.game_library_manager.gamesListLayout)
|
||||||
|
|
||||||
|
# Force layout update to ensure UI changes are visible
|
||||||
|
self.game_library_manager.gamesListWidget.updateGeometry()
|
||||||
|
if hasattr(self.game_library_manager, 'gamesListLayout'):
|
||||||
|
self.game_library_manager.gamesListLayout.update()
|
||||||
|
|
||||||
# Reload games using the existing loadGames functionality
|
# Reload games using the existing loadGames functionality
|
||||||
QTimer.singleShot(0, self.loadGames)
|
# Use a small delay to allow UI to update before starting the refresh
|
||||||
|
QTimer.singleShot(50, lambda: self.loadGames())
|
||||||
|
|
||||||
def on_search_text_changed(self, text: str):
|
def on_search_text_changed(self, text: str):
|
||||||
"""Search text change handler with debounce."""
|
"""Search text change handler with debounce."""
|
||||||
@@ -3206,7 +3229,10 @@ class MainWindow(QMainWindow):
|
|||||||
# Игра стартовала – устанавливаем флаг, обновляем кнопку на "Stop"
|
# Игра стартовала – устанавливаем флаг, обновляем кнопку на "Stop"
|
||||||
self._gameLaunched = True
|
self._gameLaunched = True
|
||||||
if self.current_running_button is not None:
|
if self.current_running_button is not None:
|
||||||
self.current_running_button.setText(_("Stop"))
|
try:
|
||||||
|
self.current_running_button.setText(_("Stop"))
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_running_button = None
|
||||||
#self._inhibit_screensaver()
|
#self._inhibit_screensaver()
|
||||||
elif not child_running:
|
elif not child_running:
|
||||||
# Игра завершилась – сбрасываем флаг, сбрасываем кнопку и останавливаем таймер
|
# Игра завершилась – сбрасываем флаг, сбрасываем кнопку и останавливаем таймер
|
||||||
@@ -3225,13 +3251,16 @@ class MainWindow(QMainWindow):
|
|||||||
Вызывается, когда игра завершилась (не по нажатию кнопки).
|
Вызывается, когда игра завершилась (не по нажатию кнопки).
|
||||||
"""
|
"""
|
||||||
if self.current_running_button is not None:
|
if self.current_running_button is not None:
|
||||||
self.current_running_button.setText(_("Play"))
|
try:
|
||||||
icon = self.theme_manager.get_icon("play")
|
self.current_running_button.setText(_("Play"))
|
||||||
if isinstance(icon, str):
|
icon = self.theme_manager.get_icon("play")
|
||||||
icon = QIcon(icon) # Convert path to QIcon
|
if isinstance(icon, str):
|
||||||
elif icon is None:
|
icon = QIcon(icon) # Convert path to QIcon
|
||||||
icon = QIcon() # Use empty QIcon as fallback
|
elif icon is None:
|
||||||
self.current_running_button.setIcon(icon)
|
icon = QIcon() # Use empty QIcon as fallback
|
||||||
|
self.current_running_button.setIcon(icon)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
self.current_running_button = None
|
self.current_running_button = None
|
||||||
self.target_exe = None
|
self.target_exe = None
|
||||||
|
|
||||||
@@ -3284,13 +3313,16 @@ class MainWindow(QMainWindow):
|
|||||||
pass
|
pass
|
||||||
self.game_processes = []
|
self.game_processes = []
|
||||||
if update_button:
|
if update_button:
|
||||||
update_button.setText(_("Play"))
|
try:
|
||||||
icon = self.theme_manager.get_icon("play")
|
update_button.setText(_("Play"))
|
||||||
if isinstance(icon, str):
|
icon = self.theme_manager.get_icon("play")
|
||||||
icon = QIcon(icon)
|
if isinstance(icon, str):
|
||||||
elif icon is None:
|
icon = QIcon(icon)
|
||||||
icon = QIcon()
|
elif icon is None:
|
||||||
update_button.setIcon(icon)
|
icon = QIcon()
|
||||||
|
update_button.setIcon(icon)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
|
if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
|
||||||
self.checkProcessTimer.stop()
|
self.checkProcessTimer.stop()
|
||||||
self.checkProcessTimer.deleteLater()
|
self.checkProcessTimer.deleteLater()
|
||||||
@@ -3312,13 +3344,16 @@ class MainWindow(QMainWindow):
|
|||||||
self.game_processes.append(process)
|
self.game_processes.append(process)
|
||||||
save_last_launch(exe_name, datetime.now())
|
save_last_launch(exe_name, datetime.now())
|
||||||
if update_button:
|
if update_button:
|
||||||
update_button.setText(_("Launching"))
|
try:
|
||||||
icon = self.theme_manager.get_icon("stop")
|
update_button.setText(_("Launching"))
|
||||||
if isinstance(icon, str):
|
icon = self.theme_manager.get_icon("stop")
|
||||||
icon = QIcon(icon)
|
if isinstance(icon, str):
|
||||||
elif icon is None:
|
icon = QIcon(icon)
|
||||||
icon = QIcon()
|
elif icon is None:
|
||||||
update_button.setIcon(icon)
|
icon = QIcon()
|
||||||
|
update_button.setIcon(icon)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
self.checkProcessTimer = QTimer(self)
|
self.checkProcessTimer = QTimer(self)
|
||||||
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
||||||
@@ -3375,13 +3410,16 @@ class MainWindow(QMainWindow):
|
|||||||
pass
|
pass
|
||||||
self.game_processes = []
|
self.game_processes = []
|
||||||
if update_button:
|
if update_button:
|
||||||
update_button.setText(_("Play"))
|
try:
|
||||||
icon = self.theme_manager.get_icon("play")
|
update_button.setText(_("Play"))
|
||||||
if isinstance(icon, str):
|
icon = self.theme_manager.get_icon("play")
|
||||||
icon = QIcon(icon)
|
if isinstance(icon, str):
|
||||||
elif icon is None:
|
icon = QIcon(icon)
|
||||||
icon = QIcon()
|
elif icon is None:
|
||||||
update_button.setIcon(icon)
|
icon = QIcon()
|
||||||
|
update_button.setIcon(icon)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
|
if hasattr(self, 'checkProcessTimer') and self.checkProcessTimer is not None:
|
||||||
self.checkProcessTimer.stop()
|
self.checkProcessTimer.stop()
|
||||||
self.checkProcessTimer.deleteLater()
|
self.checkProcessTimer.deleteLater()
|
||||||
@@ -3403,13 +3441,16 @@ class MainWindow(QMainWindow):
|
|||||||
self.game_processes.append(process)
|
self.game_processes.append(process)
|
||||||
save_last_launch(exe_name, datetime.now())
|
save_last_launch(exe_name, datetime.now())
|
||||||
if update_button:
|
if update_button:
|
||||||
update_button.setText(_("Launching"))
|
try:
|
||||||
icon = self.theme_manager.get_icon("stop")
|
update_button.setText(_("Launching"))
|
||||||
if isinstance(icon, str):
|
icon = self.theme_manager.get_icon("stop")
|
||||||
icon = QIcon(icon)
|
if isinstance(icon, str):
|
||||||
elif icon is None:
|
icon = QIcon(icon)
|
||||||
icon = QIcon()
|
elif icon is None:
|
||||||
update_button.setIcon(icon)
|
icon = QIcon()
|
||||||
|
update_button.setIcon(icon)
|
||||||
|
except RuntimeError:
|
||||||
|
pass
|
||||||
|
|
||||||
self.checkProcessTimer = QTimer(self)
|
self.checkProcessTimer = QTimer(self)
|
||||||
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
self.checkProcessTimer.timeout.connect(self.checkTargetExe)
|
||||||
|
|||||||
@@ -66,8 +66,11 @@ class VirtualKeyboard(QFrame):
|
|||||||
if not self.current_input_widget or not isinstance(self.current_input_widget, QLineEdit):
|
if not self.current_input_widget or not isinstance(self.current_input_widget, QLineEdit):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Просто устанавливаем курсор на нужную позицию без выделения
|
try:
|
||||||
self.current_input_widget.setCursorPosition(self.current_input_widget.cursorPosition())
|
# Просто устанавливаем курсор на нужную позицию без выделения
|
||||||
|
self.current_input_widget.setCursorPosition(self.current_input_widget.cursorPosition())
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
@@ -290,31 +293,43 @@ class VirtualKeyboard(QFrame):
|
|||||||
def up_key(self):
|
def up_key(self):
|
||||||
"""Перемещает курсор в QLineEdit вверх/в начало, если клавиатура видима"""
|
"""Перемещает курсор в QLineEdit вверх/в начало, если клавиатура видима"""
|
||||||
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
||||||
self.current_input_widget.setCursorPosition(0)
|
try:
|
||||||
self.current_input_widget.setFocus()
|
self.current_input_widget.setCursorPosition(0)
|
||||||
|
self.current_input_widget.setFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def down_key(self):
|
def down_key(self):
|
||||||
"""Перемещает курсор в QLineEdit вниз/в конец, если клавиатура видима"""
|
"""Перемещает курсор в QLineEdit вниз/в конец, если клавиатура видима"""
|
||||||
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
||||||
self.current_input_widget.setCursorPosition(len(self.current_input_widget.text()))
|
try:
|
||||||
self.current_input_widget.setFocus()
|
self.current_input_widget.setCursorPosition(len(self.current_input_widget.text()))
|
||||||
|
self.current_input_widget.setFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def left_key(self):
|
def left_key(self):
|
||||||
"""Перемещает курсор в QLineEdit влево, если клавиатура видима"""
|
"""Перемещает курсор в QLineEdit влево, если клавиатура видима"""
|
||||||
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
||||||
pos = self.current_input_widget.cursorPosition()
|
try:
|
||||||
if pos > 0:
|
pos = self.current_input_widget.cursorPosition()
|
||||||
self.current_input_widget.setCursorPosition(pos - 1)
|
if pos > 0:
|
||||||
self.current_input_widget.setFocus()
|
self.current_input_widget.setCursorPosition(pos - 1)
|
||||||
|
self.current_input_widget.setFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def right_key(self):
|
def right_key(self):
|
||||||
"""Перемещает курсор в QLineEdit вправо, если клавиатура видима"""
|
"""Перемещает курсор в QLineEdit вправо, если клавиатура видима"""
|
||||||
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
if self.current_input_widget and isinstance(self.current_input_widget, QLineEdit):
|
||||||
pos = self.current_input_widget.cursorPosition()
|
try:
|
||||||
text_len = len(self.current_input_widget.text())
|
pos = self.current_input_widget.cursorPosition()
|
||||||
if pos < text_len:
|
text_len = len(self.current_input_widget.text())
|
||||||
self.current_input_widget.setCursorPosition(pos + 1)
|
if pos < text_len:
|
||||||
self.current_input_widget.setFocus()
|
self.current_input_widget.setCursorPosition(pos + 1)
|
||||||
|
self.current_input_widget.setFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def move_focus_up(self):
|
def move_focus_up(self):
|
||||||
"""Перемещает фокус по кнопкам клавиатуры вверх с фиксированной скоростью"""
|
"""Перемещает фокус по кнопкам клавиатуры вверх с фиксированной скоростью"""
|
||||||
@@ -370,35 +385,41 @@ class VirtualKeyboard(QFrame):
|
|||||||
self.on_shift_click(not self.shift_pressed)
|
self.on_shift_click(not self.shift_pressed)
|
||||||
self.highlight_cursor_position()
|
self.highlight_cursor_position()
|
||||||
elif self.current_input_widget is not None:
|
elif self.current_input_widget is not None:
|
||||||
# Сохраняем текущую кнопку с фокусом
|
try:
|
||||||
focused_button = self.focusWidget()
|
# Сохраняем текущую кнопку с фокусом
|
||||||
key_to_restore = None
|
focused_button = self.focusWidget()
|
||||||
if isinstance(focused_button, QPushButton) and focused_button in self.buttons.values():
|
key_to_restore = None
|
||||||
key_to_restore = next((k for k, btn in self.buttons.items() if btn == focused_button), None)
|
if isinstance(focused_button, QPushButton) and focused_button in self.buttons.values():
|
||||||
|
key_to_restore = next((k for k, btn in self.buttons.items() if btn == focused_button), None)
|
||||||
|
|
||||||
key = "&" if key == "&&" else key
|
key = "&" if key == "&&" else key
|
||||||
cursor_pos = self.current_input_widget.cursorPosition()
|
cursor_pos = self.current_input_widget.cursorPosition()
|
||||||
text = self.current_input_widget.text()
|
text = self.current_input_widget.text()
|
||||||
new_text = text[:cursor_pos] + key + text[cursor_pos:]
|
new_text = text[:cursor_pos] + key + text[cursor_pos:]
|
||||||
self.current_input_widget.setText(new_text)
|
self.current_input_widget.setText(new_text)
|
||||||
self.current_input_widget.setCursorPosition(cursor_pos + len(key))
|
self.current_input_widget.setCursorPosition(cursor_pos + len(key))
|
||||||
self.keyPressed.emit(key)
|
self.keyPressed.emit(key)
|
||||||
self.highlight_cursor_position()
|
self.highlight_cursor_position()
|
||||||
|
|
||||||
# Если был нажат SHIFT, но не CapsLock, отключаем его после ввода символа
|
# Если был нажат SHIFT, но не CapsLock, отключаем его после ввода символа
|
||||||
if self.shift_pressed and not self.caps_lock:
|
if self.shift_pressed and not self.caps_lock:
|
||||||
self.shift_pressed = False
|
self.shift_pressed = False
|
||||||
self.update_keyboard()
|
self.update_keyboard()
|
||||||
if key_to_restore and key_to_restore in self.buttons:
|
if key_to_restore and key_to_restore in self.buttons:
|
||||||
self.buttons[key_to_restore].setFocus()
|
self.buttons[key_to_restore].setFocus()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def on_tab_click(self):
|
def on_tab_click(self):
|
||||||
if self.current_input_widget is not None:
|
if self.current_input_widget is not None:
|
||||||
self.current_input_widget.insert('\t')
|
try:
|
||||||
self.keyPressed.emit('Tab')
|
self.current_input_widget.insert('\t')
|
||||||
if self.current_input_widget:
|
self.keyPressed.emit('Tab')
|
||||||
self.current_input_widget.setFocus()
|
if self.current_input_widget:
|
||||||
self.highlight_cursor_position()
|
self.current_input_widget.setFocus()
|
||||||
|
self.highlight_cursor_position()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def on_caps_click(self):
|
def on_caps_click(self):
|
||||||
"""Включаем/выключаем CapsLock"""
|
"""Включаем/выключаем CapsLock"""
|
||||||
@@ -417,15 +438,18 @@ class VirtualKeyboard(QFrame):
|
|||||||
def on_backspace_click(self):
|
def on_backspace_click(self):
|
||||||
"""Обработка одного нажатия Backspace"""
|
"""Обработка одного нажатия Backspace"""
|
||||||
if self.current_input_widget is not None:
|
if self.current_input_widget is not None:
|
||||||
cursor_pos = self.current_input_widget.cursorPosition()
|
try:
|
||||||
text = self.current_input_widget.text()
|
cursor_pos = self.current_input_widget.cursorPosition()
|
||||||
|
text = self.current_input_widget.text()
|
||||||
|
|
||||||
if cursor_pos > 0:
|
if cursor_pos > 0:
|
||||||
new_text = text[:cursor_pos - 1] + text[cursor_pos:]
|
new_text = text[:cursor_pos - 1] + text[cursor_pos:]
|
||||||
self.current_input_widget.setText(new_text)
|
self.current_input_widget.setText(new_text)
|
||||||
self.current_input_widget.setCursorPosition(cursor_pos - 1)
|
self.current_input_widget.setCursorPosition(cursor_pos - 1)
|
||||||
self.keyPressed.emit('Backspace')
|
self.keyPressed.emit('Backspace')
|
||||||
self.highlight_cursor_position()
|
self.highlight_cursor_position()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def on_backspace_pressed(self):
|
def on_backspace_pressed(self):
|
||||||
"""Обработка зажатого Backspace"""
|
"""Обработка зажатого Backspace"""
|
||||||
@@ -449,15 +473,21 @@ class VirtualKeyboard(QFrame):
|
|||||||
# TODO: тут подумать, как обрабатывать нажатие.
|
# TODO: тут подумать, как обрабатывать нажатие.
|
||||||
# Пока болванка перехода на новую строку, в QlineEdit работает как нажатие пробела
|
# Пока болванка перехода на новую строку, в QlineEdit работает как нажатие пробела
|
||||||
if self.current_input_widget is not None:
|
if self.current_input_widget is not None:
|
||||||
self.current_input_widget.insert('\n')
|
try:
|
||||||
self.keyPressed.emit('Enter')
|
self.current_input_widget.insert('\n')
|
||||||
|
self.keyPressed.emit('Enter')
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def on_clear_click(self):
|
def on_clear_click(self):
|
||||||
"""Чистим строку от введённого текста"""
|
"""Чистим строку от введённого текста"""
|
||||||
if self.current_input_widget is not None:
|
if self.current_input_widget is not None:
|
||||||
self.current_input_widget.clear()
|
try:
|
||||||
self.keyPressed.emit('Clear')
|
self.current_input_widget.clear()
|
||||||
self.highlight_cursor_position()
|
self.keyPressed.emit('Clear')
|
||||||
|
self.highlight_cursor_position()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
def on_lang_click(self):
|
def on_lang_click(self):
|
||||||
"""Переключение раскладки"""
|
"""Переключение раскладки"""
|
||||||
@@ -483,8 +513,11 @@ class VirtualKeyboard(QFrame):
|
|||||||
def show_for_widget(self, widget):
|
def show_for_widget(self, widget):
|
||||||
self.current_input_widget = widget
|
self.current_input_widget = widget
|
||||||
if widget:
|
if widget:
|
||||||
widget.setFocus()
|
try:
|
||||||
self.highlight_cursor_position()
|
widget.setFocus()
|
||||||
|
self.highlight_cursor_position()
|
||||||
|
except RuntimeError:
|
||||||
|
self.current_input_widget = None
|
||||||
|
|
||||||
# Позиционирование клавиатуры внизу родительского виджета
|
# Позиционирование клавиатуры внизу родительского виджета
|
||||||
if self._parent and isinstance(self._parent, QWidget):
|
if self._parent and isinstance(self._parent, QWidget):
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools >= 77.0.3"]
|
requires = ["setuptools >= 75.0.0", "wheel"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "portprotonqt"
|
name = "portprotonqt"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
description = "A project to rewrite PortProton (PortWINE) using PySide"
|
description = "A project to rewrite PortProton (PortWINE) using PySide"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = { text = "GPL-3.0" }
|
license = { text = "GPL-3.0" }
|
||||||
|
|||||||
77
setup.py
Normal file
77
setup.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Setup script for PortProtonQt
|
||||||
|
Debian package build configuration
|
||||||
|
"""
|
||||||
|
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Читаем версию из pyproject.toml простым regex
|
||||||
|
pyproject_file = Path(__file__).parent / "pyproject.toml"
|
||||||
|
version_match = re.search(r'^version\s*=\s*"([^"]+)"', pyproject_file.read_text(), re.MULTILINE)
|
||||||
|
version = version_match.group(1) if version_match else "0.0.0"
|
||||||
|
|
||||||
|
# Читаем README для long_description
|
||||||
|
readme_file = Path(__file__).parent / "README.md"
|
||||||
|
long_description = readme_file.read_text(encoding="utf-8") if readme_file.exists() else ""
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name="portprotonqt",
|
||||||
|
version=version,
|
||||||
|
description="A project to rewrite PortProton (PortWINE) using PySide",
|
||||||
|
long_description=long_description,
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
author="Boria138, BlackSnaker, Mikhail Tergoev(Castro-Fidel)",
|
||||||
|
author_email="",
|
||||||
|
url="https://github.com/Castro-Fidel/PortProton",
|
||||||
|
license="GPL-3.0",
|
||||||
|
|
||||||
|
# Классификаторы PyPI
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 3 - Alpha",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
"Intended Audience :: End Users/Desktop",
|
||||||
|
"Topic :: Games/Entertainment",
|
||||||
|
],
|
||||||
|
keywords=["portproton", "wine", "game", "steam", "proton", "linux"],
|
||||||
|
|
||||||
|
# Python версия
|
||||||
|
python_requires=">=3.10",
|
||||||
|
|
||||||
|
# Пакеты
|
||||||
|
packages=find_packages(exclude=["build-aux", "dev-scripts", "documentation", "data"]),
|
||||||
|
|
||||||
|
# Включаемые файлы пакета
|
||||||
|
package_data={
|
||||||
|
"portprotonqt": [
|
||||||
|
"themes/**/*",
|
||||||
|
"themes/**/fonts/*",
|
||||||
|
"themes/**/images/*",
|
||||||
|
"themes/**/images/icons/*",
|
||||||
|
"themes/**/images/screenshots/*",
|
||||||
|
"locales/**/*",
|
||||||
|
"locales/**/*.po",
|
||||||
|
"locales/**/*.mo",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# Точка входа - исполняемый скрипт
|
||||||
|
entry_points={
|
||||||
|
"console_scripts": [
|
||||||
|
"portprotonqt=portprotonqt.app:main",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
# Дополнительные опции
|
||||||
|
include_package_data=True,
|
||||||
|
zip_safe=False,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user