6 Commits
v0.1.7 ... main

Author SHA1 Message Date
accc9b18b6 chore(localization): update
All checks were successful
Check Translations (disabled until yaspeller is fixed) / check-translations (push) Has been skipped
Code check / Check code (push) Successful in 1m23s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-10-15 15:31:56 +05:00
82249d7eab feat(settings): Added Gamepad type settings
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-10-15 15:30:31 +05:00
476c896940 chore(TODO): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-10-15 12:44:01 +05:00
b1047ba18e fix: fix card overlap on display_filter change
All checks were successful
Code check / Check code (push) Successful in 1m8s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-10-13 12:14:54 +05:00
987199d8e6 chore(release): enable node experimental-fetch
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-10-13 11:52:43 +05:00
Renovate Bot
ef1acd4581 chore(deps): update archlinux:base-devel docker digest to 06ab929
All checks were successful
Code check / Check code (push) Successful in 1m15s
2025-10-12 17:46:27 +00:00
17 changed files with 144 additions and 83 deletions

View File

@@ -94,7 +94,7 @@ jobs:
name: Build Arch Package name: Build Arch Package
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: container:
image: archlinux:base-devel@sha256:b3809917ab5a7840d42237f5f92d92660cd036bd75ae343e7825e6a24401f166 image: archlinux:base-devel@sha256:06ab929f935145dd65994a89dd06651669ea28d43c812f3e24de990978511821
volumes: volumes:
- /usr:/usr-host - /usr:/usr-host
- /opt:/opt-host - /opt:/opt-host

View File

@@ -180,6 +180,8 @@ jobs:
- name: Release - name: Release
uses: https://gitea.com/actions/gitea-release-action@v1 uses: https://gitea.com/actions/gitea-release-action@v1
env:
NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18
with: with:
body_path: changelog.txt body_path: changelog.txt
token: ${{ env.GITEA_TOKEN }} token: ${{ env.GITEA_TOKEN }}

View File

@@ -138,7 +138,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.arch == 'true' || github.event_name == 'workflow_dispatch' if: needs.changes.outputs.arch == 'true' || github.event_name == 'workflow_dispatch'
container: container:
image: archlinux:base-devel@sha256:b3809917ab5a7840d42237f5f92d92660cd036bd75ae343e7825e6a24401f166 image: archlinux:base-devel@sha256:06ab929f935145dd65994a89dd06651669ea28d43c812f3e24de990978511821
volumes: volumes:
- /usr:/usr-host - /usr:/usr-host
- /opt:/opt-host - /opt:/opt-host

15
TODO.md
View File

@@ -1,6 +1,6 @@
- [X] Адаптировать структуру проекта для поддержки инструментов сборки - [X] Адаптировать структуру проекта для поддержки инструментов сборки
- [X] Добавить возможность управления с геймпада - [X] Добавить возможность управления с геймпада
- [ ] Добавить возможность управления с тачскрина - [X] Добавить возможность управления с тачскрина (Формально и так есть)
- [X] Добавить возможность управления с мыши и клавиатуры - [X] Добавить возможность управления с мыши и клавиатуры
- [X] Добавить систему тем [Документация](documentation/theme_guide) - [X] Добавить систему тем [Документация](documentation/theme_guide)
- [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено) - [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
@@ -11,18 +11,18 @@
- [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800) - [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800)
- [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots) - [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
- [X] Получать описания и названия игр из базы данных Steam - [X] Получать описания и названия игр из базы данных Steam
- [X] Получать обложки для игр из SteamGridDB или CDN Steam - [X] Получать обложки для игр из CDN Steam
- [X] Оптимизировать работу со Steam API для ускорения времени запуска - [X] Оптимизировать работу со Steam API для ускорения времени запуска
- [X] Улучшить функцию поиска в Steam API для исправления некорректного определения ID (например, Graven определялся как ENGRAVEN или GRAVENFALL, Spore — как SporeBound или Spore Valley) - [X] Улучшить функцию поиска в Steam API для исправления некорректного определения ID (например, Graven определялся как ENGRAVEN или GRAVENFALL, Spore — как SporeBound или Spore Valley)
- [ ] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода - [X] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода
- [X] Решить проблему с ограничением Steam API в 50 тысяч игр за один запрос (иногда нужные игры не попадают в выборку и остаются без обложки) - [X] Решить проблему с ограничением Steam API в 50 тысяч игр за один запрос (иногда нужные игры не попадают в выборку и остаются без обложки)
- [X] Избавиться от вызовов yad - [X] Избавиться от вызовов yad
- [X] Реализовать собственный системный трей вместо использования трея PortProton - [X] Реализовать собственный системный трей вместо использования трея PortProton
- [X] Добавить экранную клавиатуру в поиск (реализация собственной клавиатуры слишком затратна, поэтому используется встроенная в DE клавиатура: Maliit в KDE, gjs-osk в GNOME, Squeekboard в Phosh, клавиатура SteamOS и т.д.) - [X] Добавить экранную клавиатуру в поиск
- [X] Добавить сортировку карточек по различным критериям (доступны: по недавности, количеству наигранного времени, избранному или алфавиту) - [X] Добавить сортировку карточек по различным критериям (доступны: по недавности, количеству наигранного времени, избранному или алфавиту)
- [X] Добавить индикацию запуска приложения - [X] Добавить индикацию запуска приложения
- [X] Достигнуть паритета функциональности с Ingame - [X] Достигнуть паритета функциональности с Ingame
- [ ] Достигнуть паритета функциональности с PortProton - [ ] Достигнуть паритета функциональности с PortProton (остались настройки игр и обновление скриптов)
- [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}` - [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}`
- [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/) - [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/)
- [X] Добавить переводы в переопределения - [X] Добавить переводы в переопределения
@@ -49,7 +49,7 @@
- [X] Добавить недокументированные параметры конфигурации в GUI (time_detail_level, games_sort_method, games_display_filter) - [X] Добавить недокументированные параметры конфигурации в GUI (time_detail_level, games_sort_method, games_display_filter)
- [X] Добавить систему избранного для карточек - [X] Добавить систему избранного для карточек
- [X] Заменить все `print` на `logging` - [X] Заменить все `print` на `logging`
- [ ] Привести все логи к единому языку - [X] Привести все логи к единому языку
- [X] Уменьшить количество подстановок в переводах - [X] Уменьшить количество подстановок в переводах
- [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog) - [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog)
- [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py` - [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py`
@@ -62,7 +62,6 @@
- [X] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63) - [X] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)
- [X] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)) - [X] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63))
- [X] Скопировать логику управления с D-pad на стрелки с клавиатуры - [X] Скопировать логику управления с D-pad на стрелки с клавиатуры
- [ ] Доделать светлую тему - [X] Добавить подсказки к управлению с геймпада
- [ ] Добавить подсказки к управлению с геймпада
- [X] Добавить миниатюры к выбору файлов в диалоге добавления игры - [X] Добавить миниатюры к выбору файлов в диалоге добавления игры
- [X] Добавить быстрый доступ к смонтированным дискам к выбору файлов в диалоге добавления игры - [X] Добавить быстрый доступ к смонтированным дискам к выбору файлов в диалоге добавления игры

View File

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

View File

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

View File

@@ -259,6 +259,25 @@ def save_rumble_config(rumble_enabled):
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
cp.write(configfile) cp.write(configfile)
def read_gamepad_type():
"""Reads the gamepad type from the [Gamepad] section.
Returns 'xbox' if the parameter is missing.
"""
cp = read_config_safely(CONFIG_FILE)
if cp is None or not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "type"):
save_gamepad_type("xbox")
return "xbox"
return cp.get("Gamepad", "type", fallback="xbox").lower()
def save_gamepad_type(gpad_type):
"""Saves the gamepad type to the [Gamepad] section."""
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
if "Gamepad" not in cp:
cp["Gamepad"] = {}
cp["Gamepad"]["type"] = gpad_type
with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
cp.write(configfile)
def ensure_default_proxy_config(): def ensure_default_proxy_config():
"""Ensures the [Proxy] section exists in the configuration file. """Ensures the [Proxy] section exists in the configuration file.
Creates it with empty values if missing. Creates it with empty values if missing.

View File

@@ -56,6 +56,16 @@ class GameLibraryManager:
self.is_filtering = False self.is_filtering = False
self.dirty = False self.dirty = False
def force_update_cards_library(self):
if self.gamesListWidget and self.gamesListLayout:
self.gamesListLayout.invalidate()
self.gamesListWidget.updateGeometry()
widget = self.gamesListWidget
QTimer.singleShot(0, lambda: (
widget.adjustSize(),
widget.updateGeometry()
))
def create_games_library_widget(self): def create_games_library_widget(self):
"""Creates the games library widget with search, grid, and slider.""" """Creates the games library widget with search, grid, and slider."""
self.gamesLibraryWidget = QWidget() self.gamesLibraryWidget = QWidget()
@@ -346,6 +356,8 @@ class GameLibraryManager:
self.gamesListWidget.updateGeometry() self.gamesListWidget.updateGeometry()
self.main_window._last_card_width = self.card_width self.main_window._last_card_width = self.card_width
self.force_update_cards_library()
self.is_filtering = False # Reset flag in any case self.is_filtering = False # Reset flag in any case
def _apply_filter_visibility(self, search_text: str): def _apply_filter_visibility(self, search_text: str):
@@ -453,11 +465,3 @@ class GameLibraryManager:
def filter_games_delayed(self): def filter_games_delayed(self):
"""Filters games based on search text and updates the grid.""" """Filters games based on search text and updates the grid."""
self.update_game_grid(is_filter=True) self.update_game_grid(is_filter=True)
def calculate_columns(self, card_width: int) -> int:
"""Calculate the number of columns based on card width and assumed container width."""
# Assuming a typical container width; adjust as needed
available_width = 1200 # Example width, can be dynamic if widget access is added
spacing = 15 # Assumed spacing between cards
columns = max(1, (available_width - spacing) // (card_width + spacing))
return min(columns, 8) # Cap at reasonable max

View File

@@ -12,7 +12,7 @@ from portprotonqt.logger import get_logger
from portprotonqt.image_utils import FullscreenDialog from portprotonqt.image_utils import FullscreenDialog
from portprotonqt.custom_widgets import NavLabel, AutoSizeButton from portprotonqt.custom_widgets import NavLabel, AutoSizeButton
from portprotonqt.game_card import GameCard from portprotonqt.game_card import GameCard
from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config, read_gamepad_type
from portprotonqt.dialogs import AddGameDialog, WinetricksDialog from portprotonqt.dialogs import AddGameDialog, WinetricksDialog
from portprotonqt.virtual_keyboard import VirtualKeyboard from portprotonqt.virtual_keyboard import VirtualKeyboard
@@ -87,8 +87,13 @@ class InputManager(QObject):
super().__init__(cast(QObject, main_window)) super().__init__(cast(QObject, main_window))
self._parent = main_window self._parent = main_window
self._gamepad_handling_enabled = True self._gamepad_handling_enabled = True
self.gamepad_type = GamepadType.UNKNOWN type_str = read_gamepad_type()
# Ensure attributes exist on main_window if type_str == "playstation":
self.gamepad_type = GamepadType.PLAYSTATION
elif type_str == "xbox":
self.gamepad_type = GamepadType.XBOX
else:
self.gamepad_type = GamepadType.UNKNOWN
self._parent.currentDetailPage = getattr(self._parent, 'currentDetailPage', None) self._parent.currentDetailPage = getattr(self._parent, 'currentDetailPage', None)
self._parent.current_exec_line = getattr(self._parent, 'current_exec_line', None) self._parent.current_exec_line = getattr(self._parent, 'current_exec_line', None)
self._parent.current_add_game_dialog = getattr(self._parent, 'current_add_game_dialog', None) self._parent.current_add_game_dialog = getattr(self._parent, 'current_add_game_dialog', None)
@@ -271,38 +276,6 @@ class InputManager(QObject):
elif current_row_idx == 0: elif current_row_idx == 0:
self._parent.tabButtons[tab_index].setFocus(Qt.FocusReason.OtherFocusReason) self._parent.tabButtons[tab_index].setFocus(Qt.FocusReason.OtherFocusReason)
def detect_gamepad_type(self, device: InputDevice) -> GamepadType:
"""
Определяет тип геймпада по capabilities
"""
caps = device.capabilities()
keys = set(caps.get(ecodes.EV_KEY, []))
# Для EV_ABS вытаскиваем только коды (первый элемент кортежа)
abs_axes = {a if isinstance(a, int) else a[0] for a in caps.get(ecodes.EV_ABS, [])}
# Xbox layout
if {ecodes.BTN_SOUTH, ecodes.BTN_EAST, ecodes.BTN_NORTH, ecodes.BTN_WEST}.issubset(keys):
if {ecodes.ABS_X, ecodes.ABS_Y, ecodes.ABS_RX, ecodes.ABS_RY}.issubset(abs_axes):
self.gamepad_type = GamepadType.XBOX
return GamepadType.XBOX
# PlayStation layout
if ecodes.BTN_TOUCH in keys or (ecodes.BTN_DPAD_UP in keys and ecodes.BTN_EAST in keys):
self.gamepad_type = GamepadType.PLAYSTATION
logger.info(f"Detected {self.gamepad_type.value} controller: {device.name}")
return GamepadType.PLAYSTATION
# Steam Controller / Deck (трекпады)
if any(a for a in abs_axes if a >= ecodes.ABS_MT_SLOT):
self.gamepad_type = GamepadType.XBOX
logger.info(f"Detected {self.gamepad_type.value} controller: {device.name}")
return GamepadType.XBOX
# Fallback
self.gamepad_type = GamepadType.XBOX
return GamepadType.XBOX
def enable_file_explorer_mode(self, file_explorer): def enable_file_explorer_mode(self, file_explorer):
"""Настройка обработки геймпада для FileExplorer""" """Настройка обработки геймпада для FileExplorer"""
try: try:
@@ -1369,8 +1342,6 @@ class InputManager(QObject):
new_gamepad = self.find_gamepad() new_gamepad = self.find_gamepad()
if new_gamepad and new_gamepad != self.gamepad: if new_gamepad and new_gamepad != self.gamepad:
logger.info(f"Gamepad connected: {new_gamepad.name}") logger.info(f"Gamepad connected: {new_gamepad.name}")
self.detect_gamepad_type(new_gamepad)
logger.info(f"Detected gamepad type: {self.gamepad_type.value}")
self.stop_rumble() self.stop_rumble()
self.gamepad = new_gamepad self.gamepad = new_gamepad
if self.gamepad_thread: if self.gamepad_thread:

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-10-12 17:14+0500\n" "POT-Creation-Date: 2025-10-15 15:31+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"
@@ -579,6 +579,9 @@ msgstr ""
msgid "Games Display Filter:" msgid "Games Display Filter:"
msgstr "" msgstr ""
msgid "Gamepad Type:"
msgstr ""
msgid "Proxy URL" msgid "Proxy URL"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-10-12 17:14+0500\n" "POT-Creation-Date: 2025-10-15 15:31+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"
@@ -579,6 +579,9 @@ msgstr ""
msgid "Games Display Filter:" msgid "Games Display Filter:"
msgstr "" msgstr ""
msgid "Gamepad Type:"
msgstr ""
msgid "Proxy URL" msgid "Proxy URL"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PortProtonQt 0.1.1\n" "Project-Id-Version: PortProtonQt 0.1.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-10-12 17:14+0500\n" "POT-Creation-Date: 2025-10-15 15:31+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"
@@ -577,6 +577,9 @@ msgstr ""
msgid "Games Display Filter:" msgid "Games Display Filter:"
msgstr "" msgstr ""
msgid "Gamepad Type:"
msgstr ""
msgid "Proxy URL" msgid "Proxy URL"
msgstr "" msgstr ""

View File

@@ -9,8 +9,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-10-12 17:14+0500\n" "POT-Creation-Date: 2025-10-15 15:31+0500\n"
"PO-Revision-Date: 2025-10-12 17:13+0500\n" "PO-Revision-Date: 2025-10-15 15:31+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"
@@ -588,6 +588,9 @@ msgstr "все"
msgid "Games Display Filter:" msgid "Games Display Filter:"
msgstr "Фильтр игр:" msgstr "Фильтр игр:"
msgid "Gamepad Type:"
msgstr "Тип геймпада:"
msgid "Proxy URL" msgid "Proxy URL"
msgstr "Адрес прокси" msgstr "Адрес прокси"

View File

@@ -29,7 +29,7 @@ from portprotonqt.config_utils import (
read_display_filter, read_favorites, save_favorites, save_time_config, save_sort_method, read_display_filter, read_favorites, save_favorites, save_time_config, save_sort_method,
save_display_filter, save_proxy_config, read_proxy_config, read_fullscreen_config, save_display_filter, save_proxy_config, read_proxy_config, read_fullscreen_config,
save_fullscreen_config, read_window_geometry, save_window_geometry, reset_config, save_fullscreen_config, read_window_geometry, save_window_geometry, reset_config,
clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config, read_gamepad_type, save_gamepad_type
) )
from portprotonqt.localization import _, get_egs_language, read_metadata_translations from portprotonqt.localization import _, get_egs_language, read_metadata_translations
from portprotonqt.howlongtobeat_api import HowLongToBeat from portprotonqt.howlongtobeat_api import HowLongToBeat
@@ -100,6 +100,7 @@ class MainWindow(QMainWindow):
self.games_load_timer.timeout.connect(self.finalize_game_loading) self.games_load_timer.timeout.connect(self.finalize_game_loading)
self.games_loaded.connect(self.on_games_loaded) self.games_loaded.connect(self.on_games_loaded)
self.current_add_game_dialog = None self.current_add_game_dialog = None
self.current_display_filter = read_display_filter()
self.settingsDebounceTimer = QTimer(self) self.settingsDebounceTimer = QTimer(self)
self.settingsDebounceTimer.setSingleShot(True) self.settingsDebounceTimer.setSingleShot(True)
@@ -820,6 +821,24 @@ class MainWindow(QMainWindow):
for i, btn in self.tabButtons.items(): for i, btn in self.tabButtons.items():
btn.setChecked(i == index) btn.setChecked(i == index)
self.stackedWidget.setCurrentIndex(index) self.stackedWidget.setCurrentIndex(index)
if hasattr(self, "game_library_manager"):
mgr = self.game_library_manager
if mgr.gamesListWidget and mgr.gamesListLayout:
layout = mgr.gamesListLayout
widget = mgr.gamesListWidget
QTimer.singleShot(0, lambda: (
layout.invalidate(),
widget.adjustSize(),
widget.updateGeometry()
))
if hasattr(self, "autoInstallContainer") and hasattr(self, "autoInstallContainerLayout"):
layout = self.autoInstallContainerLayout
widget = self.autoInstallContainer
QTimer.singleShot(0, lambda: (
layout.invalidate(),
widget.adjustSize(),
widget.updateGeometry()
))
def openSystemOverlay(self): def openSystemOverlay(self):
"""Opens the system overlay dialog.""" """Opens the system overlay dialog."""
@@ -1765,7 +1784,22 @@ class MainWindow(QMainWindow):
self.gamesDisplayCombo.setCurrentIndex(idx) self.gamesDisplayCombo.setCurrentIndex(idx)
formLayout.addRow(self.gamesDisplayTitle, self.gamesDisplayCombo) formLayout.addRow(self.gamesDisplayTitle, self.gamesDisplayCombo)
# 4. Proxy settings # 4 Gamepad Type
self.gamepadTypeCombo = QComboBox()
self.gamepadTypeCombo.addItems(["Xbox", "PlayStation"])
self.gamepadTypeCombo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.gamepadTypeCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
self.gamepadTypeTitle = QLabel(_("Gamepad Type:"))
self.gamepadTypeTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
self.gamepadTypeTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
current_type_str = read_gamepad_type()
if current_type_str == "playstation":
self.gamepadTypeCombo.setCurrentText("PlayStation")
else:
self.gamepadTypeCombo.setCurrentText("Xbox")
formLayout.addRow(self.gamepadTypeTitle, self.gamepadTypeCombo)
# 5. Proxy settings
self.proxyUrlEdit = CustomLineEdit(self, theme=self.theme) self.proxyUrlEdit = CustomLineEdit(self, theme=self.theme)
self.proxyUrlEdit.setPlaceholderText(_("Proxy URL")) self.proxyUrlEdit.setPlaceholderText(_("Proxy URL"))
self.proxyUrlEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE) self.proxyUrlEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
@@ -1797,7 +1831,7 @@ class MainWindow(QMainWindow):
self.proxyPasswordTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.proxyPasswordTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
formLayout.addRow(self.proxyPasswordTitle, self.proxyPasswordEdit) formLayout.addRow(self.proxyPasswordTitle, self.proxyPasswordEdit)
# 5. Fullscreen setting for application # 6. Fullscreen setting for application
self.fullscreenCheckBox = QCheckBox(_("Launch Application in Fullscreen")) self.fullscreenCheckBox = QCheckBox(_("Launch Application in Fullscreen"))
self.fullscreenCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) self.fullscreenCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
self.fullscreenCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.fullscreenCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
@@ -1808,7 +1842,7 @@ class MainWindow(QMainWindow):
self.fullscreenCheckBox.setChecked(current_fullscreen) self.fullscreenCheckBox.setChecked(current_fullscreen)
formLayout.addRow(self.fullscreenTitle, self.fullscreenCheckBox) formLayout.addRow(self.fullscreenTitle, self.fullscreenCheckBox)
# 6. Automatic fullscreen on gamepad connection # 7. Automatic fullscreen on gamepad connection
self.autoFullscreenGamepadCheckBox = QCheckBox(_("Auto Fullscreen on Gamepad connected")) self.autoFullscreenGamepadCheckBox = QCheckBox(_("Auto Fullscreen on Gamepad connected"))
self.autoFullscreenGamepadCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) self.autoFullscreenGamepadCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
self.autoFullscreenGamepadCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.autoFullscreenGamepadCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
@@ -1820,7 +1854,7 @@ class MainWindow(QMainWindow):
self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen) self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen)
formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox) formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox)
# 7. Gamepad haptic feedback config # 8. Gamepad haptic feedback config
self.gamepadRumbleCheckBox = QCheckBox(_("Gamepad haptic feedback")) self.gamepadRumbleCheckBox = QCheckBox(_("Gamepad haptic feedback"))
self.gamepadRumbleCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.gamepadRumbleCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.gamepadRumbleCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) self.gamepadRumbleCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
@@ -1831,7 +1865,7 @@ class MainWindow(QMainWindow):
self.gamepadRumbleCheckBox.setChecked(current_rumble_state) self.gamepadRumbleCheckBox.setChecked(current_rumble_state)
formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox) formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox)
# # 8. Legendary Authentication # # 9. Legendary Authentication
# self.legendaryAuthButton = AutoSizeButton( # self.legendaryAuthButton = AutoSizeButton(
# _("Open Legendary Login"), # _("Open Legendary Login"),
# icon=self.theme_manager.get_icon("login")self.theme_manager.get_icon("login") # icon=self.theme_manager.get_icon("login")self.theme_manager.get_icon("login")
@@ -1978,9 +2012,12 @@ class MainWindow(QMainWindow):
def applySettingsDelayed(self): def applySettingsDelayed(self):
read_time_config() read_time_config()
self.games = []
self.loadGames()
display_filter = read_display_filter() display_filter = read_display_filter()
reload_needed = display_filter != self.current_display_filter
if reload_needed:
self.games = []
self.loadGames()
self.current_display_filter = display_filter
for card in self.game_library_manager.game_card_cache.values(): for card in self.game_library_manager.game_card_cache.values():
card.update_badge_visibility(display_filter) card.update_badge_visibility(display_filter)
@@ -1995,6 +2032,8 @@ class MainWindow(QMainWindow):
filter_idx = self.gamesDisplayCombo.currentIndex() filter_idx = self.gamesDisplayCombo.currentIndex()
filter_key = self.filter_keys[filter_idx] filter_key = self.filter_keys[filter_idx]
old_filter = self.current_display_filter
save_display_filter(filter_key) save_display_filter(filter_key)
proxy_url = self.proxyUrlEdit.text().strip() proxy_url = self.proxyUrlEdit.text().strip()
@@ -2011,17 +2050,32 @@ class MainWindow(QMainWindow):
rumble_enabled = self.gamepadRumbleCheckBox.isChecked() rumble_enabled = self.gamepadRumbleCheckBox.isChecked()
save_rumble_config(rumble_enabled) save_rumble_config(rumble_enabled)
for card in self.game_library_manager.game_card_cache.values(): gamepad_type_text = self.gamepadTypeCombo.currentText()
card.update_badge_visibility(filter_key) gpad_type = "playstation" if gamepad_type_text == "PlayStation" else "xbox"
save_gamepad_type(gpad_type)
if self.currentDetailPage and self.current_exec_line: if hasattr(self, 'input_manager'):
current_game = next((game for game in self.games if game[4] == self.current_exec_line), None) if gpad_type == "playstation":
if current_game: self.input_manager.gamepad_type = GamepadType.PLAYSTATION
self.stackedWidget.removeWidget(self.currentDetailPage) elif gpad_type == "xbox":
self.currentDetailPage.deleteLater() self.input_manager.gamepad_type = GamepadType.XBOX
self.currentDetailPage = None else:
self.openGameDetailPage(*current_game) self.input_manager.gamepad_type = GamepadType.UNKNOWN
self.updateControlHints()
if filter_key != old_filter:
for card in self.game_library_manager.game_card_cache.values():
card.update_badge_visibility(filter_key)
if self.currentDetailPage and self.current_exec_line:
current_game = next((game for game in self.games if game[4] == self.current_exec_line), None)
if current_game:
self.stackedWidget.removeWidget(self.currentDetailPage)
self.currentDetailPage.deleteLater()
self.currentDetailPage = None
self.openGameDetailPage(*current_game)
self.current_display_filter = filter_key
self.settingsDebounceTimer.start() self.settingsDebounceTimer.start()
gamepad_connected = self.input_manager.find_gamepad() is not None gamepad_connected = self.input_manager.find_gamepad() is not None