forked from Boria138/PortProtonQt
		
	Compare commits
	
		
			11 Commits
		
	
	
		
			4d6f32f053
			...
			accc9b18b6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| accc9b18b6 | |||
| 82249d7eab | |||
| 476c896940 | |||
| b1047ba18e | |||
| 987199d8e6 | |||
|  | ef1acd4581 | ||
| 96f884904c | |||
| b856a2afae | |||
| 55ef0030e6 | |||
| 8aaeaa4824 | |||
| f55372b480 | 
| @@ -94,7 +94,7 @@ jobs: | ||||
|     name: Build Arch Package | ||||
|     runs-on: ubuntu-22.04 | ||||
|     container: | ||||
|       image: archlinux:base-devel@sha256:b3809917ab5a7840d42237f5f92d92660cd036bd75ae343e7825e6a24401f166 | ||||
|       image: archlinux:base-devel@sha256:06ab929f935145dd65994a89dd06651669ea28d43c812f3e24de990978511821 | ||||
|       volumes: | ||||
|         - /usr:/usr-host | ||||
|         - /opt:/opt-host | ||||
|   | ||||
| @@ -8,7 +8,7 @@ on: | ||||
|  | ||||
| env: | ||||
|   # Common version, will be used for tagging the release | ||||
|   VERSION: 0.1.6 | ||||
|   VERSION: 0.1.7 | ||||
|   PKGDEST: "/tmp/portprotonqt" | ||||
|   PACKAGE: "portprotonqt" | ||||
|   GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} | ||||
| @@ -180,6 +180,8 @@ jobs: | ||||
|  | ||||
|       - name: Release | ||||
|         uses: https://gitea.com/actions/gitea-release-action@v1 | ||||
|         env: | ||||
|             NODE_OPTIONS: '--experimental-fetch' # if nodejs < 18 | ||||
|         with: | ||||
|           body_path: changelog.txt | ||||
|           token: ${{ env.GITEA_TOKEN }} | ||||
|   | ||||
| @@ -138,7 +138,7 @@ jobs: | ||||
|     needs: changes | ||||
|     if: needs.changes.outputs.arch == 'true' || github.event_name == 'workflow_dispatch' | ||||
|     container: | ||||
|       image: archlinux:base-devel@sha256:b3809917ab5a7840d42237f5f92d92660cd036bd75ae343e7825e6a24401f166 | ||||
|       image: archlinux:base-devel@sha256:06ab929f935145dd65994a89dd06651669ea28d43c812f3e24de990978511821 | ||||
|       volumes: | ||||
|         - /usr:/usr-host | ||||
|         - /opt:/opt-host | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| Все заметные изменения в этом проекте фиксируются в этом файле. | ||||
| Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/). | ||||
|  | ||||
| ## [Unreleased] | ||||
| ## [0.1.7] - 2025-10-12 | ||||
|  | ||||
| ### Added | ||||
| - Возможность скроллинга библиотеки мышью или пальцем | ||||
| @@ -11,8 +11,9 @@ | ||||
| - Диалог для управление Winetricks | ||||
| - Кнопки для удаления префикса, wine или proton | ||||
| - Все настройки Wine с оригинального PortProton | ||||
| - Виртуальная клавиатура в диалог добавления игры и поиск по библиотеке | ||||
| - Виртуальная клавиатура в диалог добавления игры и поиск по библиотеке и автоустановках | ||||
| - Вкладка автоустановок | ||||
| - В заголовке окна теперь отображается версия приложения и хеш коммита если запуск идёт с гита | ||||
|  | ||||
| ### Changed | ||||
| - Проведён рефакторинг и оптимизация всего что связано с карточками и библиотекой игр | ||||
| @@ -28,6 +29,8 @@ | ||||
| - При сохранении настроек теперь не меняется размер окна | ||||
|  | ||||
| ### Contributors | ||||
| - @wmigor (Igor Akulov) | ||||
| - @Vector_null | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
							
								
								
									
										15
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								TODO.md
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| - [X] Адаптировать структуру проекта для поддержки инструментов сборки | ||||
| - [X] Добавить возможность управления с геймпада | ||||
| - [ ] Добавить возможность управления с тачскрина | ||||
| - [X] Добавить возможность управления с тачскрина (Формально и так есть) | ||||
| - [X] Добавить возможность управления с мыши и клавиатуры | ||||
| - [X] Добавить систему тем [Документация](documentation/theme_guide) | ||||
| - [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено) | ||||
| @@ -11,18 +11,18 @@ | ||||
| - [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800) | ||||
| - [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots) | ||||
| - [X] Получать описания и названия игр из базы данных Steam | ||||
| - [X] Получать обложки для игр из SteamGridDB или CDN Steam | ||||
| - [X] Получать обложки для игр из CDN Steam | ||||
| - [X] Оптимизировать работу со Steam API для ускорения времени запуска | ||||
| - [X] Улучшить функцию поиска в Steam API для исправления некорректного определения ID (например, Graven определялся как ENGRAVEN или GRAVENFALL, Spore — как SporeBound или Spore Valley) | ||||
| - [ ] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода | ||||
| - [X] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода | ||||
| - [X] Решить проблему с ограничением Steam API в 50 тысяч игр за один запрос (иногда нужные игры не попадают в выборку и остаются без обложки) | ||||
| - [X] Избавиться от вызовов yad | ||||
| - [X] Реализовать собственный системный трей вместо использования трея PortProton | ||||
| - [X] Добавить экранную клавиатуру в поиск (реализация собственной клавиатуры слишком затратна, поэтому используется встроенная в DE клавиатура: Maliit в KDE, gjs-osk в GNOME, Squeekboard в Phosh, клавиатура SteamOS и т.д.) | ||||
| - [X] Добавить экранную клавиатуру в поиск | ||||
| - [X] Добавить сортировку карточек по различным критериям (доступны: по недавности, количеству наигранного времени, избранному или алфавиту) | ||||
| - [X] Добавить индикацию запуска приложения | ||||
| - [X] Достигнуть паритета функциональности с Ingame | ||||
| - [ ] Достигнуть паритета функциональности с PortProton | ||||
| - [ ] Достигнуть паритета функциональности с PortProton (остались настройки игр и обновление скриптов) | ||||
| - [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}` | ||||
| - [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/) | ||||
| - [X] Добавить переводы в переопределения | ||||
| @@ -49,7 +49,7 @@ | ||||
| - [X] Добавить недокументированные параметры конфигурации в GUI (time_detail_level, games_sort_method, games_display_filter) | ||||
| - [X] Добавить систему избранного для карточек | ||||
| - [X] Заменить все `print` на `logging` | ||||
| - [ ] Привести все логи к единому языку | ||||
| - [X] Привести все логи к единому языку | ||||
| - [X] Уменьшить количество подстановок в переводах | ||||
| - [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog) | ||||
| - [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py` | ||||
| @@ -62,7 +62,6 @@ | ||||
| - [X] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63) | ||||
| - [X] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)) | ||||
| - [X] Скопировать логику управления с D-pad на стрелки с клавиатуры | ||||
| - [ ] Доделать светлую тему | ||||
| - [ ] Добавить подсказки к управлению с геймпада | ||||
| - [X] Добавить подсказки к управлению с геймпада | ||||
| - [X] Добавить миниатюры к выбору файлов в диалоге добавления игры | ||||
| - [X] Добавить быстрый доступ к смонтированным дискам к выбору файлов в диалоге добавления игры | ||||
|   | ||||
| @@ -36,7 +36,7 @@ AppDir: | ||||
|     id: ru.linux_gaming.PortProtonQt | ||||
|     name: PortProtonQt | ||||
|     icon: ru.linux_gaming.PortProtonQt | ||||
|     version: 0.1.6 | ||||
|     version: 0.1.7 | ||||
|     exec: usr/bin/python3 | ||||
|     exec_args: "-m portprotonqt.app $@" | ||||
|   apt: | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| pkgname=portprotonqt | ||||
| pkgver=0.1.6 | ||||
| pkgver=0.1.7 | ||||
| pkgrel=1 | ||||
| pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store" | ||||
| arch=('any') | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| %global pypi_name portprotonqt | ||||
| %global pypi_version 0.1.6 | ||||
| %global pypi_version 0.1.7 | ||||
| %global oname PortProtonQt | ||||
| %global _python_no_extras_requires 1 | ||||
|  | ||||
|   | ||||
| @@ -21,9 +21,9 @@ Current translation status: | ||||
|  | ||||
| | Locale | Progress | Translated | | ||||
| | :----- | -------: | ---------: | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 239 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 239 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 239 of 239 | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 241 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 241 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 241 of 241 | | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -21,9 +21,9 @@ | ||||
|  | ||||
| | Локаль | Прогресс | Переведено | | ||||
| | :----- | -------: | ---------: | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 239 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 239 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 239 из 239 | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 241 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 241 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 241 из 241 | | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -11,10 +11,19 @@ from portprotonqt.cli import parse_args | ||||
|  | ||||
| __app_id__ = "ru.linux_gaming.PortProtonQt" | ||||
| __app_name__ = "PortProtonQt" | ||||
| __app_version__ = "0.1.6" | ||||
| __app_version__ = "0.1.7" | ||||
|  | ||||
| def get_version(): | ||||
|     try: | ||||
|         commit = subprocess.check_output( | ||||
|             ['git', 'rev-parse', '--short', 'HEAD'], | ||||
|             stderr=subprocess.DEVNULL | ||||
|         ).decode('utf-8').strip() | ||||
|         return f"{__app_version__} ({commit})" | ||||
|     except (subprocess.CalledProcessError, FileNotFoundError, OSError): | ||||
|         return __app_version__ | ||||
|  | ||||
| def main(): | ||||
|  | ||||
|     os.environ['PW_CLI'] = '1' | ||||
|     os.environ['PROCESS_LOG'] = '1' | ||||
|     os.environ['START_FROM_STEAM'] = '1' | ||||
| @@ -49,7 +58,8 @@ def main(): | ||||
|     else: | ||||
|         logger.warning(f"Qt translations for {system_locale.name()} not found in {translations_path}, using english language") | ||||
|  | ||||
|     window = MainWindow(app_name=__app_name__) | ||||
|     version = get_version() | ||||
|     window = MainWindow(app_name=__app_name__, version=version) | ||||
|  | ||||
|     if args.fullscreen: | ||||
|         logger.info("Launching in fullscreen mode due to --fullscreen flag") | ||||
|   | ||||
| @@ -259,6 +259,25 @@ def save_rumble_config(rumble_enabled): | ||||
|     with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: | ||||
|         cp.write(configfile) | ||||
|  | ||||
| def read_gamepad_type(): | ||||
|     """Reads the gamepad type from the [Gamepad] section. | ||||
|     Returns 'xbox' if the parameter is missing. | ||||
|     """ | ||||
|     cp = read_config_safely(CONFIG_FILE) | ||||
|     if cp is None or not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "type"): | ||||
|         save_gamepad_type("xbox") | ||||
|         return "xbox" | ||||
|     return cp.get("Gamepad", "type", fallback="xbox").lower() | ||||
|  | ||||
| def save_gamepad_type(gpad_type): | ||||
|     """Saves the gamepad type to the [Gamepad] section.""" | ||||
|     cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser() | ||||
|     if "Gamepad" not in cp: | ||||
|         cp["Gamepad"] = {} | ||||
|     cp["Gamepad"]["type"] = gpad_type | ||||
|     with open(CONFIG_FILE, "w", encoding="utf-8") as configfile: | ||||
|         cp.write(configfile) | ||||
|  | ||||
| def ensure_default_proxy_config(): | ||||
|     """Ensures the [Proxy] section exists in the configuration file. | ||||
|     Creates it with empty values if missing. | ||||
|   | ||||
| @@ -56,6 +56,16 @@ class GameLibraryManager: | ||||
|         self.is_filtering = 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): | ||||
|         """Creates the games library widget with search, grid, and slider.""" | ||||
|         self.gamesLibraryWidget = QWidget() | ||||
| @@ -346,6 +356,8 @@ class GameLibraryManager: | ||||
|                 self.gamesListWidget.updateGeometry() | ||||
|                 self.main_window._last_card_width = self.card_width | ||||
|  | ||||
|                 self.force_update_cards_library() | ||||
|  | ||||
|         self.is_filtering = False  # Reset flag in any case | ||||
|  | ||||
|     def _apply_filter_visibility(self, search_text: str): | ||||
| @@ -453,11 +465,3 @@ class GameLibraryManager: | ||||
|     def filter_games_delayed(self): | ||||
|         """Filters games based on search text and updates the grid.""" | ||||
|         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 | ||||
|   | ||||
| @@ -12,7 +12,7 @@ from portprotonqt.logger import get_logger | ||||
| from portprotonqt.image_utils import FullscreenDialog | ||||
| from portprotonqt.custom_widgets import NavLabel, AutoSizeButton | ||||
| 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.virtual_keyboard import VirtualKeyboard | ||||
|  | ||||
| @@ -87,8 +87,13 @@ class InputManager(QObject): | ||||
|         super().__init__(cast(QObject, main_window)) | ||||
|         self._parent = main_window | ||||
|         self._gamepad_handling_enabled = True | ||||
|         self.gamepad_type = GamepadType.UNKNOWN | ||||
|         # Ensure attributes exist on main_window | ||||
|         type_str = read_gamepad_type() | ||||
|         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.current_exec_line = getattr(self._parent, 'current_exec_line', 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: | ||||
|                     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): | ||||
|         """Настройка обработки геймпада для FileExplorer""" | ||||
|         try: | ||||
| @@ -1369,8 +1342,6 @@ class InputManager(QObject): | ||||
|             new_gamepad = self.find_gamepad() | ||||
|             if new_gamepad and new_gamepad != self.gamepad: | ||||
|                 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.gamepad = new_gamepad | ||||
|                 if self.gamepad_thread: | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-12 15:20+0500\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: de_DE\n" | ||||
| @@ -419,6 +419,10 @@ msgstr "" | ||||
| msgid "Failed to start installation." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Processed {} installation..." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Installation completed successfully." | ||||
| msgstr "" | ||||
|  | ||||
| @@ -575,6 +579,9 @@ msgstr "" | ||||
| msgid "Games Display Filter:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Gamepad Type:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Proxy URL" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-12 15:20+0500\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: es_ES\n" | ||||
| @@ -419,6 +419,10 @@ msgstr "" | ||||
| msgid "Failed to start installation." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Processed {} installation..." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Installation completed successfully." | ||||
| msgstr "" | ||||
|  | ||||
| @@ -575,6 +579,9 @@ msgstr "" | ||||
| msgid "Games Display Filter:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Gamepad Type:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Proxy URL" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PortProtonQt 0.1.1\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-12 15:20+0500\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -417,6 +417,10 @@ msgstr "" | ||||
| msgid "Failed to start installation." | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Processed {} installation..." | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Installation completed successfully." | ||||
| msgstr "" | ||||
|  | ||||
| @@ -573,6 +577,9 @@ msgstr "" | ||||
| msgid "Games Display Filter:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Gamepad Type:" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Proxy URL" | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,8 +9,8 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-12 15:20+0500\n" | ||||
| "PO-Revision-Date: 2025-10-12 15:20+0500\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "PO-Revision-Date: 2025-10-15 15:31+0500\n" | ||||
| "Last-Translator: \n" | ||||
| "Language: ru_RU\n" | ||||
| "Language-Team: ru_RU <LL@li.org>\n" | ||||
| @@ -426,6 +426,10 @@ msgstr "Установка уже выполняется." | ||||
| msgid "Failed to start installation." | ||||
| msgstr "Не удалось запустить установку." | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Processed {} installation..." | ||||
| msgstr "В процессе установки {}..." | ||||
|  | ||||
| msgid "Installation completed successfully." | ||||
| msgstr "Установка завершена успешно." | ||||
|  | ||||
| @@ -584,6 +588,9 @@ msgstr "все" | ||||
| msgid "Games Display Filter:" | ||||
| msgstr "Фильтр игр:" | ||||
|  | ||||
| msgid "Gamepad Type:" | ||||
| msgstr "Тип геймпада:" | ||||
|  | ||||
| msgid "Proxy URL" | ||||
| msgstr "Адрес прокси" | ||||
|  | ||||
|   | ||||
| @@ -29,7 +29,7 @@ from portprotonqt.config_utils import ( | ||||
|     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_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.howlongtobeat_api import HowLongToBeat | ||||
| @@ -54,7 +54,7 @@ class MainWindow(QMainWindow): | ||||
|     update_progress = Signal(int) | ||||
|     update_status_message = Signal(str, int) | ||||
|  | ||||
|     def __init__(self, app_name: str): | ||||
|     def __init__(self, app_name: str, version: str): | ||||
|         super().__init__() | ||||
|         self.theme_manager = ThemeManager() | ||||
|         self.is_exiting = False | ||||
| @@ -64,7 +64,7 @@ class MainWindow(QMainWindow): | ||||
|         self.tray_manager = TrayManager(self, app_name, self.current_theme_name) | ||||
|         self.card_width = read_card_size() | ||||
|         self._last_card_width = self.card_width | ||||
|         self.setWindowTitle(app_name) | ||||
|         self.setWindowTitle(f"{app_name} {version}") | ||||
|         self.setMinimumSize(800, 600) | ||||
|  | ||||
|         self.games = [] | ||||
| @@ -100,6 +100,7 @@ class MainWindow(QMainWindow): | ||||
|         self.games_load_timer.timeout.connect(self.finalize_game_loading) | ||||
|         self.games_loaded.connect(self.on_games_loaded) | ||||
|         self.current_add_game_dialog = None | ||||
|         self.current_display_filter = read_display_filter() | ||||
|  | ||||
|         self.settingsDebounceTimer = QTimer(self) | ||||
|         self.settingsDebounceTimer.setSingleShot(True) | ||||
| @@ -467,7 +468,7 @@ class MainWindow(QMainWindow): | ||||
|             return | ||||
|         self.progress_bar.setVisible(True) | ||||
|         self.progress_bar.setRange(0, 0)  # Indeterminate | ||||
|         self.update_status_message.emit(f"Processed {script_name} installation...", 0) | ||||
|         self.update_status_message.emit(_("Processed {} installation...").format(script_name), 0) | ||||
|         self.install_monitor_timer = QTimer(self) | ||||
|         self.install_monitor_timer.timeout.connect(self.monitor_install_progress) | ||||
|         self.install_monitor_timer.start(2000)  # Start monitoring after 2s | ||||
| @@ -820,6 +821,24 @@ class MainWindow(QMainWindow): | ||||
|         for i, btn in self.tabButtons.items(): | ||||
|             btn.setChecked(i == 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): | ||||
|         """Opens the system overlay dialog.""" | ||||
| @@ -1063,13 +1082,13 @@ class MainWindow(QMainWindow): | ||||
|         autoInstallPage = QWidget() | ||||
|         autoInstallPage.setStyleSheet(self.theme.LIBRARY_WIDGET_STYLE) | ||||
|         autoInstallLayout = QVBoxLayout(autoInstallPage) | ||||
|         autoInstallLayout.setContentsMargins(0, 0, 0, 0) | ||||
|         autoInstallLayout.setContentsMargins(20, 0, 20, 0) | ||||
|         autoInstallLayout.setSpacing(0) | ||||
|  | ||||
|         # Верхняя панель с заголовком и поиском | ||||
|         headerWidget = QWidget() | ||||
|         headerLayout = QHBoxLayout(headerWidget) | ||||
|         headerLayout.setContentsMargins(20, 10, 20, 10) | ||||
|         headerLayout.setContentsMargins(0, 10, 0, 10) | ||||
|         headerLayout.setSpacing(10) | ||||
|  | ||||
|         # Заголовок | ||||
| @@ -1765,7 +1784,22 @@ class MainWindow(QMainWindow): | ||||
|         self.gamesDisplayCombo.setCurrentIndex(idx) | ||||
|         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.setPlaceholderText(_("Proxy URL")) | ||||
|         self.proxyUrlEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE) | ||||
| @@ -1797,7 +1831,7 @@ class MainWindow(QMainWindow): | ||||
|         self.proxyPasswordTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus) | ||||
|         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.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) | ||||
|         self.fullscreenCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
| @@ -1808,7 +1842,7 @@ class MainWindow(QMainWindow): | ||||
|         self.fullscreenCheckBox.setChecked(current_fullscreen) | ||||
|         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.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) | ||||
|         self.autoFullscreenGamepadCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
| @@ -1820,7 +1854,7 @@ class MainWindow(QMainWindow): | ||||
|         self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen) | ||||
|         formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox) | ||||
|  | ||||
|         # 7. Gamepad haptic feedback config | ||||
|         # 8. Gamepad haptic feedback config | ||||
|         self.gamepadRumbleCheckBox = QCheckBox(_("Gamepad haptic feedback")) | ||||
|         self.gamepadRumbleCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus) | ||||
|         self.gamepadRumbleCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE) | ||||
| @@ -1831,7 +1865,7 @@ class MainWindow(QMainWindow): | ||||
|         self.gamepadRumbleCheckBox.setChecked(current_rumble_state) | ||||
|         formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox) | ||||
|  | ||||
|         # # 8. Legendary Authentication | ||||
|         # # 9. Legendary Authentication | ||||
|         # self.legendaryAuthButton = AutoSizeButton( | ||||
|         #     _("Open Legendary Login"), | ||||
|         #     icon=self.theme_manager.get_icon("login")self.theme_manager.get_icon("login") | ||||
| @@ -1978,9 +2012,12 @@ class MainWindow(QMainWindow): | ||||
|  | ||||
|     def applySettingsDelayed(self): | ||||
|         read_time_config() | ||||
|         self.games = [] | ||||
|         self.loadGames() | ||||
|         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(): | ||||
|             card.update_badge_visibility(display_filter) | ||||
|  | ||||
| @@ -1995,6 +2032,8 @@ class MainWindow(QMainWindow): | ||||
|  | ||||
|         filter_idx = self.gamesDisplayCombo.currentIndex() | ||||
|         filter_key = self.filter_keys[filter_idx] | ||||
|  | ||||
|         old_filter = self.current_display_filter | ||||
|         save_display_filter(filter_key) | ||||
|  | ||||
|         proxy_url = self.proxyUrlEdit.text().strip() | ||||
| @@ -2011,17 +2050,32 @@ class MainWindow(QMainWindow): | ||||
|         rumble_enabled = self.gamepadRumbleCheckBox.isChecked() | ||||
|         save_rumble_config(rumble_enabled) | ||||
|  | ||||
|         for card in self.game_library_manager.game_card_cache.values(): | ||||
|             card.update_badge_visibility(filter_key) | ||||
|         gamepad_type_text = self.gamepadTypeCombo.currentText() | ||||
|         gpad_type = "playstation" if gamepad_type_text == "PlayStation" else "xbox" | ||||
|         save_gamepad_type(gpad_type) | ||||
|  | ||||
|         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) | ||||
|         if hasattr(self, 'input_manager'): | ||||
|             if gpad_type == "playstation": | ||||
|                 self.input_manager.gamepad_type = GamepadType.PLAYSTATION | ||||
|             elif gpad_type == "xbox": | ||||
|                 self.input_manager.gamepad_type = GamepadType.XBOX | ||||
|             else: | ||||
|                 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() | ||||
|  | ||||
|         gamepad_connected = self.input_manager.find_gamepad() is not None | ||||
|   | ||||
| @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" | ||||
|  | ||||
| [project] | ||||
| name = "portprotonqt" | ||||
| version = "0.1.6" | ||||
| version = "0.1.7" | ||||
| description = "A project to rewrite PortProton (PortWINE) using PySide" | ||||
| readme = "README.md" | ||||
| license = { text = "GPL-3.0" } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user