From 2ae3831662ee9c5cb2305fef9ccd4c7920e3294b Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 10:39:50 +0500
Subject: [PATCH 01/47] fix(input_manager): restore keyboard navigation  with
 Up/Down keys

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index daaafbc..6a97dc8 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -390,6 +390,23 @@ class InputManager(QObject):
                 focused._show_context_menu(pos)
                 return True
 
+        # Handle Up/Down keys for non-GameCard tabs
+        if key in (Qt.Key.Key_Up, Qt.Key.Key_Down) and not isinstance(focused, GameCard):
+            page = self._parent.stackedWidget.currentWidget()
+            if key == Qt.Key.Key_Down:
+                if isinstance(focused, NavLabel):
+                    focusables = page.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
+                    focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
+                    if focusables:
+                        focusables[0].setFocus()
+                        return True
+                elif focused:
+                    focused.focusNextChild()
+                    return True
+            elif key == Qt.Key.Key_Up and focused:
+                focused.focusPreviousChild()
+                return True
+
         # Tab switching with Left/Right keys (non-GameCard focus or no focus)
         idx = self._parent.stackedWidget.currentIndex()
         total = len(self._parent.tabButtons)

From c037af43145bdd54e8286e1427d561de361d8e2c Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 11:21:51 +0500
Subject: [PATCH 02/47] feat(input_manager): Added QMenu handler for Gamepad

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 41 +++++++++++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 4 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 6a97dc8..55c9674 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -3,7 +3,7 @@ import threading
 from typing import Protocol, cast
 from evdev import InputDevice, ecodes, list_devices
 import pyudev
-from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog
+from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu
 from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot
 from PySide6.QtGui import QKeyEvent
 from portprotonqt.logger import get_logger
@@ -37,8 +37,8 @@ BUTTONS = {
     'confirm':   {ecodes.BTN_A},
     'back':      {ecodes.BTN_B},
     'add_game':  {ecodes.BTN_Y},
-    'prev_tab':  {ecodes.BTN_TL, ecodes.BTN_TRIGGER_HAPPY7},
-    'next_tab':  {ecodes.BTN_TR, ecodes.BTN_TRIGGER_HAPPY5},
+    'prev_tab':  {ecodes.BTN_TL},
+    'next_tab':  {ecodes.BTN_TR},
     'confirm_stick': {ecodes.BTN_THUMBL, ecodes.BTN_THUMBR},
     'context_menu': {ecodes.BTN_START},
     'menu':      {ecodes.BTN_SELECT},
@@ -129,6 +129,21 @@ class InputManager(QObject):
                 return
             active = QApplication.activeWindow()
             focused = QApplication.focusWidget()
+            popup = QApplication.activePopupWidget()
+
+            # Handle QMenu (context menu)
+            if isinstance(popup, QMenu):
+                if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+                    # Trigger the currently highlighted menu action and close the menu
+                    if popup.activeAction():
+                        popup.activeAction().trigger()
+                        popup.close()
+                    return
+                elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
+                    # Close the menu
+                    popup.close()
+                    return
+                return
 
             # Закрытие AddGameDialog на кнопку B
             if button_code in BUTTONS['back'] and isinstance(active, QDialog):
@@ -149,7 +164,9 @@ class InputManager(QObject):
             if isinstance(focused, GameCard):
                 if button_code in BUTTONS['context_menu']:
                     pos = QPoint(focused.width() // 2, focused.height() // 2)
-                    focused._show_context_menu(pos)
+                    menu = focused._show_context_menu(pos)
+                    if menu:
+                        menu.setFocus(Qt.FocusReason.OtherFocusReason)
                     return
 
             # Game launch on detail page
@@ -188,6 +205,22 @@ class InputManager(QObject):
             if not app:
                 return
             active = QApplication.activeWindow()
+            popup = QApplication.activePopupWidget()
+
+            # Handle QMenu navigation with D-pad
+            if isinstance(popup, QMenu):
+                if code == ecodes.ABS_HAT0Y and value != 0:
+                    actions = popup.actions()
+                    if actions:
+                        current_idx = actions.index(popup.activeAction()) if popup.activeAction() in actions else 0
+                        if value < 0:  # Up
+                            next_idx = (current_idx - 1) % len(actions)
+                            popup.setActiveAction(actions[next_idx])
+                        elif value > 0:  # Down
+                            next_idx = (current_idx + 1) % len(actions)
+                            popup.setActiveAction(actions[next_idx])
+                    return
+                return
 
             # Fullscreen horizontal navigation
             if isinstance(active, FullscreenDialog) and code == ecodes.ABS_HAT0X:

From 364e1dd02a316a7e6a721e29c16a64f84cc2dfd2 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 15:16:42 +0500
Subject: [PATCH 03/47] feat(input_manager): Added QComboBox and QListView
 handler for Gamepad

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/context_menu_manager.py |  2 +-
 portprotonqt/input_manager.py        | 61 +++++++++++++++++++++++++---
 2 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py
index 6d9eca2..a524c05 100644
--- a/portprotonqt/context_menu_manager.py
+++ b/portprotonqt/context_menu_manager.py
@@ -9,6 +9,7 @@ from PySide6.QtGui import QDesktopServices
 from portprotonqt.config_utils import parse_desktop_entry
 from portprotonqt.localization import _
 from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam
+from portprotonqt.dialogs import AddGameDialog
 
 class ContextMenuManager:
     """Manages context menu actions for game management in PortProtonQT."""
@@ -321,7 +322,6 @@ class ContextMenuManager:
 
     def edit_game_shortcut(self, game_name, exec_line, cover_path):
         """Opens the AddGameDialog in edit mode to modify an existing .desktop file."""
-        from portprotonqt.dialogs import AddGameDialog  # Local import to avoid circular dependency
 
         if not self._check_portproton():
             return
diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 55c9674..e030ad3 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -3,7 +3,7 @@ import threading
 from typing import Protocol, cast
 from evdev import InputDevice, ecodes, list_devices
 import pyudev
-from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu
+from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView
 from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot
 from PySide6.QtGui import QKeyEvent
 from portprotonqt.logger import get_logger
@@ -134,20 +134,55 @@ class InputManager(QObject):
             # Handle QMenu (context menu)
             if isinstance(popup, QMenu):
                 if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
-                    # Trigger the currently highlighted menu action and close the menu
                     if popup.activeAction():
                         popup.activeAction().trigger()
                         popup.close()
                     return
                 elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
-                    # Close the menu
                     popup.close()
                     return
                 return
 
+            # Handle QComboBox
+            if isinstance(focused, QComboBox):
+                if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+                    focused.showPopup()
+                return
+
+            # Handle QListView
+            if isinstance(focused, QListView):
+                combo = None
+                parent = focused.parentWidget()
+                while parent:
+                    if isinstance(parent, QComboBox):
+                        combo = parent
+                        break
+                    parent = parent.parentWidget()
+
+                if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+                    idx = focused.currentIndex()
+                    if idx.isValid():
+                        if combo:
+                            combo.setCurrentIndex(idx.row())
+                            combo.hidePopup()
+                            combo.setFocus(Qt.FocusReason.OtherFocusReason)
+                        else:
+                            focused.activated.emit(idx)
+                            focused.clicked.emit(idx)
+                            focused.hide()
+                    return
+
+                if button_code in BUTTONS['back']:
+                    if combo:
+                        combo.hidePopup()
+                        combo.setFocus(Qt.FocusReason.OtherFocusReason)
+                    else:
+                        focused.clearSelection()
+                        focused.hide()
+
             # Закрытие AddGameDialog на кнопку B
             if button_code in BUTTONS['back'] and isinstance(active, QDialog):
-                active.reject()  # Закрываем диалог
+                active.reject()
                 return
 
             # FullscreenDialog
@@ -193,7 +228,6 @@ class InputManager(QObject):
         except Exception as e:
             logger.error(f"Error in handle_button_slot: {e}", exc_info=True)
 
-
     @Slot(int, int, float)
     def handle_dpad_slot(self, code: int, value: int, current_time: float) -> None:
         try:
@@ -205,6 +239,7 @@ class InputManager(QObject):
             if not app:
                 return
             active = QApplication.activeWindow()
+            focused = QApplication.focusWidget()
             popup = QApplication.activePopupWidget()
 
             # Handle QMenu navigation with D-pad
@@ -222,6 +257,22 @@ class InputManager(QObject):
                     return
                 return
 
+            # Handle QListView navigation with D-pad
+            if isinstance(focused, QListView) and code == ecodes.ABS_HAT0Y and value != 0:
+                model = focused.model()
+                current_index = focused.currentIndex()
+                if model and current_index.isValid():
+                    row_count = model.rowCount()
+                    current_row = current_index.row()
+                    if value > 0:  # Down
+                        next_row = min(current_row + 1, row_count - 1)
+                        focused.setCurrentIndex(model.index(next_row, current_index.column()))
+                    elif value < 0:  # Up
+                        prev_row = max(current_row - 1, 0)
+                        focused.setCurrentIndex(model.index(prev_row, current_index.column()))
+                    focused.scrollTo(focused.currentIndex(), QListView.ScrollHint.PositionAtCenter)
+                return
+
             # Fullscreen horizontal navigation
             if isinstance(active, FullscreenDialog) and code == ecodes.ABS_HAT0X:
                 if value < 0:

From 0f59c46d362baa81f79490231fc2f4ac23b7035a Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 15:26:37 +0500
Subject: [PATCH 04/47] fix(input_manager): handle AddGameDialog navigation
 with D-pad

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 29 +++++++++++++++++++++++++----
 portprotonqt/main_window.py   |  1 +
 2 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index e030ad3..2f0caff 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -216,7 +216,9 @@ class InputManager(QObject):
             elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
                 self._parent.goBackDetailPage(getattr(self._parent, 'currentDetailPage', None))
             elif button_code in BUTTONS['add_game']:
-                self._parent.openAddGameDialog()
+                # Only open AddGameDialog if in library tab (index 0)
+                if self._parent.stackedWidget.currentIndex() == 0:
+                    self._parent.openAddGameDialog()
             elif button_code in BUTTONS['prev_tab']:
                 idx = (self._parent.stackedWidget.currentIndex() - 1) % len(self._parent.tabButtons)
                 self._parent.switchTab(idx)
@@ -242,6 +244,21 @@ class InputManager(QObject):
             focused = QApplication.focusWidget()
             popup = QApplication.activePopupWidget()
 
+            # Handle AddGameDialog navigation with D-pad
+            if isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0:
+                if not focused or not active.focusWidget():
+                    # If no widget is focused, focus the first focusable widget
+                    focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
+                    focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
+                    if focusables:
+                        focusables[0].setFocus(Qt.FocusReason.OtherFocusReason)
+                    return
+                if value > 0:  # Down
+                    active.focusNextChild()
+                elif value < 0:  # Up
+                    active.focusPreviousChild()
+                return
+
             # Handle QMenu navigation with D-pad
             if isinstance(popup, QMenu):
                 if code == ecodes.ABS_HAT0Y and value != 0:
@@ -364,7 +381,6 @@ class InputManager(QObject):
                                     next_card.setFocus()
                                     if scroll_area:
                                         scroll_area.ensureWidgetVisible(next_card, 50, 50)
-
                 elif code == ecodes.ABS_HAT0Y and value != 0:  # Up/Down
                     if value > 0:  # Down
                         next_row_idx = current_row_idx + 1
@@ -621,6 +637,9 @@ class InputManager(QObject):
                 if focusables:
                     focusables[0].setFocus()
                     return True
+            elif focused:
+                focused.focusNextChild()
+                return True
         # Navigate up through tab content
         if key == Qt.Key.Key_Up:
             if isinstance(focused, NavLabel):
@@ -641,8 +660,10 @@ class InputManager(QObject):
         elif key == Qt.Key.Key_E:
             if isinstance(focused, QLineEdit):
                 return False
-            self._parent.openAddGameDialog()
-            return True
+            # Only open AddGameDialog if in library tab (index 0)
+            if self._parent.stackedWidget.currentIndex() == 0:
+                self._parent.openAddGameDialog()
+                return True
 
         # Toggle fullscreen with F11
         if key == Qt.Key.Key_F11:
diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index b31f398..77cb547 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -742,6 +742,7 @@ class MainWindow(QMainWindow):
             return
 
         dialog = AddGameDialog(self, self.theme)
+        dialog.setFocus(Qt.FocusReason.OtherFocusReason)
         self.current_add_game_dialog = dialog  # Сохраняем ссылку на диалог
 
         # Предзаполняем путь к .exe при drag-and-drop

From 9c4ad0b7bacac08849aff9036561de7b88a9bad2 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 15:28:41 +0500
Subject: [PATCH 05/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a6472f4..d352e6b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@
 - Пункт в контекстное меню "Удалить из Steam”
 - Метод сортировки сначала избранное
 - Настройка автоматического перехода в режим полноэкранного отображения приложения при подключении геймпада (по умолчанию отключено)
+- Обработчики для QMenu и QComboBox на геймпаде
 
 ### Changed
 - Обновлены все иконки
@@ -39,6 +40,7 @@
 - Карточки теперь фокусируются в направлении движения стрелок или D-pad, например если нажать D-pad вниз то перейдёшь на карточку со следующей колонки, а не по порядку
 - D-pad больше не переключает вкладки только RB и LB
 - Кнопка добавления игры больше не фокусируется
+- Диалог добавления игры теперь открывается только в библиотеке
 
 ### Fixed
 - Обработка несуществующей темы с возвратом к “standart”

From 3e493571528b6791d93b60cda7fac5d4f2204684 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 19:02:24 +0500
Subject: [PATCH 06/47] feat(build): add appstream metainfo files

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 README.md                                     |  3 +-
 build-aux/fedora-git.spec                     |  6 +-
 build-aux/fedora.spec                         |  6 +-
 .../ru.linux_gaming.PortProtonQt.metainfo.xml | 61 +++++++++++++++++++
 4 files changed, 71 insertions(+), 5 deletions(-)
 create mode 100644 build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml

diff --git a/README.md b/README.md
index 87e3dcb..598d136 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@
 ## В планах
 
 - [X] Адаптировать структуру проекта для поддержки инструментов сборки
-- [ ] Добавить возможность управление с геймпада
+- [X] Добавить возможность управление с геймпада
 - [ ] Добавить возможность управление с тачскрина
 - [X] Добавить возможность управление с мыши и клавиатуры
 - [X] Добавить систему тем [Документация](documentation/theme_guide)
@@ -16,6 +16,7 @@
 - [ ] Продумать систему вкладок вместо той что есть сейчас
 - [ ] Добавить Gamescope сессию на подобие той что есть в SteamOS
 - [ ] Написать адаптивный дизайн (За эталон берём SteamDeck с разрешением 1280х800)
+- [ ] Переделать скриншоты для соответсвия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
 - [X] Брать описание и названия игр с базы данных Steam
 - [X] Брать обложки для игр со SteamGridDB или CDN Steam
 - [X] Оптимизировать работу со SteamApi что бы ускорить время запуска
diff --git a/build-aux/fedora-git.spec b/build-aux/fedora-git.spec
index 3eace5a..feb0d77 100644
--- a/build-aux/fedora-git.spec
+++ b/build-aux/fedora-git.spec
@@ -45,7 +45,7 @@ Requires:       perl-Image-ExifTool
 Requires:       xdg-utils
 
 %description -n python3-%{pypi_name}-git
-PortProtonQt is a modern, user-friendly graphical interface designed to streamline the management and launching of games across multiple platforms, including 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.
 
 %prep
 git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git
@@ -62,6 +62,8 @@ cp -r build-aux/share %{buildroot}/usr/
 
 %files -n python3-%{pypi_name}-git -f %{pyproject_files}
 %{_bindir}/%{pypi_name}
-%{_datadir}/*
+%{_datadir}/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg
+%{_metainfodir}/ru.linux_gaming.PortProtonQt.metainfo.xml
+%{_datadir}/applications/ru.linux_gaming.PortProtonQt.desktop
 
 %changelog
diff --git a/build-aux/fedora.spec b/build-aux/fedora.spec
index 24c5f1c..ba26d25 100644
--- a/build-aux/fedora.spec
+++ b/build-aux/fedora.spec
@@ -42,7 +42,7 @@ Requires:       perl-Image-ExifTool
 Requires:       xdg-utils
 
 %description -n python3-%{pypi_name}
-PortProtonQt is a modern, user-friendly graphical interface designed to streamline the management and launching of games across multiple platforms, including 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.
 
 %prep
 git clone https://git.linux-gaming.ru/Boria138/PortProtonQt
@@ -61,6 +61,8 @@ cp -r build-aux/share %{buildroot}/usr/
 
 %files -n python3-%{pypi_name} -f %{pyproject_files}
 %{_bindir}/%{pypi_name}
-%{_datadir}/*
+%{_datadir}/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg
+%{_metainfodir}/ru.linux_gaming.PortProtonQt.metainfo.xml
+%{_datadir}/applications/ru.linux_gaming.PortProtonQt.desktop
 
 %changelog
diff --git a/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml b/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml
new file mode 100644
index 0000000..42af19e
--- /dev/null
+++ b/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop">
+  <name>PortProtonQt</name>
+  <id>ru.linux_gaming.PortProtonQt</id>
+  <metadata_license>CC0-1.0</metadata_license>
+  <project_license>GPL-3.0-or-later</project_license>
+  <summary>Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store</summary>
+  <summary xml:lang="ru">Современный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store</summary>
+  <description>
+    <p>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.</p>
+  </description>
+  <launchable type="desktop-id">ru.linux_gaming.PortProtonQt.desktop</launchable>
+  <developer id="ru.linux_gaming">
+    <name>Boria138</name>
+  </developer>
+  <recommends>
+    <control>keyboard</control>
+    <control>pointing</control>
+    <control>touch</control>
+    <control>gamepad</control>
+  </recommends>
+  <branding>
+    <color type="primary" scheme_preference="light">#007AFF</color>
+    <color type="primary" scheme_preference="dark">#09BEC8</color>
+  </branding>
+  <categories>
+    <category>Game</category>
+    <category>Utility</category>
+  </categories>
+  <url type="homepage">https://git.linux-gaming.ru/Boria138/PortProtonQt</url>
+  <url type="bugtracker">https://git.linux-gaming.ru/Boria138/PortProtonQt/issues</url>
+  <screenshots>
+    <screenshot type="default">
+      <image>https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/portprotonqt/themes/standart/images/screenshots/%D0%91%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA%D0%B0.png</image>
+      <caption>Library</caption>
+      <caption xml:lang="ru">Библиотека</caption>
+    </screenshot>
+    <screenshot>
+      <image>https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/portprotonqt/themes/standart/images/screenshots/%D0%9A%D0%B0%D1%80%D1%82%D0%BE%D1%87%D0%BA%D0%B0.png</image>
+      <caption>Card detail page</caption>
+      <caption xml:lang="ru">Детали игры</caption>
+    </screenshot>
+    <screenshot>
+      <image>https://git.linux-gaming.ru/Boria138/PortProtonQt/src/commit/9c4ad0b7bacac08849aff9036561de7b88a9bad2/portprotonqt/themes/standart/images/screenshots/%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8.png</image>
+      <caption>Settings</caption>
+      <caption xml:lang="ru">Настройки</caption>
+    </screenshot>
+  </screenshots>
+  <keywords>
+    <keyword translate="no">wine</keyword>
+    <keyword translate="no">proton</keyword>
+    <keyword translate="no">steam</keyword>
+    <keyword translate="no">windows</keyword>
+    <keyword translate="no">epic games store</keyword>
+    <keyword translate="no">egs</keyword>
+    <keyword translate="no">qt</keyword>
+    <keyword translate="no">portproton</keyword>
+    <keyword>games</keyword>
+    </keywords>
+    <content_rating type="oars-1.1" />
+</component>

From a5977f0f59cdf585e21614a514e3943961ffd158 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 19:08:53 +0500
Subject: [PATCH 07/47] fix: correct badge positioning in
 GameCard.update_badge_visibility

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/game_card.py | 48 ++++++++++++++++-----------------------
 1 file changed, 20 insertions(+), 28 deletions(-)

diff --git a/portprotonqt/game_card.py b/portprotonqt/game_card.py
index e24e3be..64e7468 100644
--- a/portprotonqt/game_card.py
+++ b/portprotonqt/game_card.py
@@ -272,35 +272,27 @@ class GameCard(QFrame):
         top_y = 10
         badge_y_positions = []
         badge_width = int(self.coverLabel.width() * 2/3)
-        if self.steam_visible:
-            steam_x = self.coverLabel.width() - badge_width - right_margin
-            self.steamLabel.move(steam_x, top_y)
-            badge_y_positions.append(top_y + self.steamLabel.height())
-        if self.egs_visible:
-            egs_x = self.coverLabel.width() - badge_width - right_margin
-            egs_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
-            self.egsLabel.move(egs_x, egs_y)
-            badge_y_positions.append(egs_y + self.egsLabel.height())
-        if self.portproton_visible:
-            portproton_x = self.coverLabel.width() - badge_width - right_margin
-            portproton_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
-            self.portprotonLabel.move(portproton_x, portproton_y)
-            badge_y_positions.append(portproton_y + self.portprotonLabel.height())
-        if self.protondbLabel.isVisible():
-            protondb_x = self.coverLabel.width() - badge_width - right_margin
-            protondb_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
-            self.protondbLabel.move(protondb_x, protondb_y)
-            badge_y_positions.append(protondb_y + self.protondbLabel.height())
-        if self.anticheatLabel.isVisible():
-            anticheat_x = self.coverLabel.width() - badge_width - right_margin
-            anticheat_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
-            self.anticheatLabel.move(anticheat_x, anticheat_y)
 
-            self.anticheatLabel.raise_()
-            self.protondbLabel.raise_()
-            self.portprotonLabel.raise_()
-            self.egsLabel.raise_()
-            self.steamLabel.raise_()
+        badges = [
+            (self.steam_visible, self.steamLabel),
+            (self.egs_visible, self.egsLabel),
+            (self.portproton_visible, self.portprotonLabel),
+            (self.protondbLabel.isVisible(), self.protondbLabel),
+            (self.anticheatLabel.isVisible(), self.anticheatLabel),
+        ]
+
+        for is_visible, badge in badges:
+            if is_visible:
+                badge_x = self.coverLabel.width() - badge_width - right_margin
+                badge_y = badge_y_positions[-1] + badge_spacing if badge_y_positions else top_y
+                badge.move(badge_x, badge_y)
+                badge_y_positions.append(badge_y + badge.height())
+
+        self.anticheatLabel.raise_()
+        self.protondbLabel.raise_()
+        self.portprotonLabel.raise_()
+        self.egsLabel.raise_()
+        self.steamLabel.raise_()
 
     def _show_context_menu(self, pos):
         """Delegate context menu display to ContextMenuManager."""

From 2377426b27c3ba39a6210f3e45f830a672ee2a0f Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 21:31:07 +0500
Subject: [PATCH 08/47] fix: correct badge positioning in GameCard on display
 filter change (again)

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/game_card.py | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/portprotonqt/game_card.py b/portprotonqt/game_card.py
index 64e7468..e9699a7 100644
--- a/portprotonqt/game_card.py
+++ b/portprotonqt/game_card.py
@@ -266,13 +266,6 @@ class GameCard(QFrame):
         self.egsLabel.setVisible(self.egs_visible)
         self.portprotonLabel.setVisible(self.portproton_visible)
 
-        # Reposition badges
-        right_margin = 8
-        badge_spacing = 5
-        top_y = 10
-        badge_y_positions = []
-        badge_width = int(self.coverLabel.width() * 2/3)
-
         badges = [
             (self.steam_visible, self.steamLabel),
             (self.egs_visible, self.egsLabel),
@@ -281,6 +274,12 @@ class GameCard(QFrame):
             (self.anticheatLabel.isVisible(), self.anticheatLabel),
         ]
 
+        right_margin = 8
+        badge_spacing = 5
+        top_y = 10
+        badge_y_positions = []
+        badge_width = int(self.coverLabel.width() * 2/3)
+
         for is_visible, badge in badges:
             if is_visible:
                 badge_x = self.coverLabel.width() - badge_width - right_margin

From 83455bc33f163dce94dd0b4383a55bc464a48557 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 21:40:27 +0500
Subject: [PATCH 09/47] fix: restore correct badge positioning on visibility
 change in GameCard

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/game_card.py | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/portprotonqt/game_card.py b/portprotonqt/game_card.py
index e9699a7..42b558a 100644
--- a/portprotonqt/game_card.py
+++ b/portprotonqt/game_card.py
@@ -261,19 +261,26 @@ class GameCard(QFrame):
         self.steam_visible = (str(self.game_source).lower() == "steam" and display_filter in ("all", "favorites"))
         self.egs_visible = (str(self.game_source).lower() == "epic" and display_filter in ("all", "favorites"))
         self.portproton_visible = (str(self.game_source).lower() == "portproton" and display_filter in ("all", "favorites"))
+        protondb_visible = bool(self.getProtonDBText(self.protondb_tier))
+        anticheat_visible = bool(self.getAntiCheatText(self.anticheat_status))
 
+        # Обновляем видимость бейджей
         self.steamLabel.setVisible(self.steam_visible)
         self.egsLabel.setVisible(self.egs_visible)
         self.portprotonLabel.setVisible(self.portproton_visible)
+        self.protondbLabel.setVisible(protondb_visible)
+        self.anticheatLabel.setVisible(anticheat_visible)
 
+        # Подготавливаем список всех бейджей с их текущей видимостью
         badges = [
             (self.steam_visible, self.steamLabel),
             (self.egs_visible, self.egsLabel),
             (self.portproton_visible, self.portprotonLabel),
-            (self.protondbLabel.isVisible(), self.protondbLabel),
-            (self.anticheatLabel.isVisible(), self.anticheatLabel),
+            (protondb_visible, self.protondbLabel),
+            (anticheat_visible, self.anticheatLabel),
         ]
 
+        # Пересчитываем позиции бейджей
         right_margin = 8
         badge_spacing = 5
         top_y = 10
@@ -287,6 +294,7 @@ class GameCard(QFrame):
                 badge.move(badge_x, badge_y)
                 badge_y_positions.append(badge_y + badge.height())
 
+        # Поднимаем бейджи в правильном порядке (от нижнего к верхнему)
         self.anticheatLabel.raise_()
         self.protondbLabel.raise_()
         self.portprotonLabel.raise_()

From bcf319c0241580ffd2b2910614897cc8266570f5 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sat, 7 Jun 2025 22:11:36 +0500
Subject: [PATCH 10/47] feat: optimize slider code

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/main_window.py | 26 +++++++-------------------
 1 file changed, 7 insertions(+), 19 deletions(-)

diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 77cb547..df5a9fa 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -539,6 +539,12 @@ class MainWindow(QMainWindow):
     def startSearchDebounce(self, text):
         self.searchDebounceTimer.start()
 
+    def on_slider_value_changed(self, value: int):
+            self.card_width = value
+            self.sizeSlider.setToolTip(f"{value} px")
+            save_card_size(value)
+            self.updateGameGrid()
+
     def filterGamesDelayed(self):
         """Filters games based on search text and updates the grid."""
         text = self.searchEdit.text().strip().lower()
@@ -579,33 +585,16 @@ class MainWindow(QMainWindow):
         self.sizeSlider.setFixedWidth(150)
         self.sizeSlider.setToolTip(f"{self.card_width} px")
         self.sizeSlider.setStyleSheet(self.theme.SLIDER_SIZE_STYLE)
+        self.sizeSlider.valueChanged.connect(self.on_slider_value_changed)
         sliderLayout.addWidget(self.sizeSlider)
         layout.addLayout(sliderLayout)
 
-        self.sliderDebounceTimer = QTimer(self)
-        self.sliderDebounceTimer.setSingleShot(True)
-        self.sliderDebounceTimer.setInterval(40)
-
-        def on_slider_value_changed():
-            self.setUpdatesEnabled(False)
-            self.card_width = self.sizeSlider.value()
-            self.sizeSlider.setToolTip(f"{self.card_width} px")
-            self.updateGameGrid()
-            self.setUpdatesEnabled(True)
-        self.sizeSlider.valueChanged.connect(lambda val: self.sliderDebounceTimer.start())
-        self.sliderDebounceTimer.timeout.connect(on_slider_value_changed)
-
         def calculate_card_width():
             available_width = scrollArea.width() - 20
             spacing = self.gamesListLayout._spacing
             target_cards_per_row = 8
             calculated_width = (available_width - spacing * (target_cards_per_row - 1)) // target_cards_per_row
             calculated_width = max(200, min(calculated_width, 250))
-            if not self.sizeSlider.value() == self.card_width:
-                self.card_width = calculated_width
-                self.sizeSlider.setValue(self.card_width)
-                self.sizeSlider.setToolTip(f"{self.card_width} px")
-                self.updateGameGrid()
 
         QTimer.singleShot(0, calculate_card_width)
 
@@ -621,7 +610,6 @@ class MainWindow(QMainWindow):
             self._last_width = self.width()
         if abs(self.width() - self._last_width) > 10:
             self._last_width = self.width()
-            self.sliderDebounceTimer.start()
 
     def loadVisibleImages(self):
         visible_region = self.gamesListWidget.visibleRegion()

From 4de4bdb99ddda46a28b7381616fb6c22b0d70645 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 07:16:02 +0500
Subject: [PATCH 11/47] feat: added system overlay to guide button

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py  |  11 +++-
 portprotonqt/main_window.py    |   6 ++
 portprotonqt/system_overlay.py | 100 +++++++++++++++++++++++++++++++++
 3 files changed, 116 insertions(+), 1 deletion(-)
 create mode 100644 portprotonqt/system_overlay.py

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 2f0caff..a9e29d5 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -25,6 +25,8 @@ class MainWindowProtocol(Protocol):
         ...
     def toggleGame(self, exec_line: str | None, button: QWidget | None = None) -> None:
         ...
+    def openSystemOverlay(self) -> None:
+            ...
     stackedWidget: QStackedWidget
     tabButtons: dict[int, QWidget]
     gamesListWidget: QWidget
@@ -42,6 +44,7 @@ BUTTONS = {
     'confirm_stick': {ecodes.BTN_THUMBL, ecodes.BTN_THUMBR},
     'context_menu': {ecodes.BTN_START},
     'menu':      {ecodes.BTN_SELECT},
+    'guide':     {ecodes.BTN_MODE},
 }
 
 class InputManager(QObject):
@@ -131,6 +134,12 @@ class InputManager(QObject):
             focused = QApplication.focusWidget()
             popup = QApplication.activePopupWidget()
 
+            # Handle Guide button to open system overlay
+            if button_code in BUTTONS['guide']:
+                if not popup and not isinstance(active, QDialog):
+                    self._parent.openSystemOverlay()
+                    return
+
             # Handle QMenu (context menu)
             if isinstance(popup, QMenu):
                 if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
@@ -244,7 +253,7 @@ class InputManager(QObject):
             focused = QApplication.focusWidget()
             popup = QApplication.activePopupWidget()
 
-            # Handle AddGameDialog navigation with D-pad
+            # Handle SystemOverlay or AddGameDialog navigation with D-pad
             if isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0:
                 if not focused or not active.focusWidget():
                     # If no widget is focused, focus the first focusable widget
diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index df5a9fa..90a5f6b 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -13,6 +13,7 @@ from portprotonqt.game_card import GameCard
 from portprotonqt.custom_widgets import FlowLayout, ClickableLabel, AutoSizeButton, NavLabel
 from portprotonqt.input_manager import InputManager
 from portprotonqt.context_menu_manager import ContextMenuManager
+from portprotonqt.system_overlay import SystemOverlay
 
 from portprotonqt.image_utils import load_pixmap_async, round_corners, ImageCarousel
 from portprotonqt.steam_api import get_steam_game_info_async, get_full_steam_game_info_async, get_steam_installed_games
@@ -500,6 +501,11 @@ class MainWindow(QMainWindow):
             btn.setChecked(i == index)
         self.stackedWidget.setCurrentIndex(index)
 
+    def openSystemOverlay(self):
+        """Opens the system overlay dialog."""
+        overlay = SystemOverlay(self, self.theme)
+        overlay.exec()
+
     def createSearchWidget(self) -> tuple[QWidget, QLineEdit]:
         self.container = QWidget()
         self.container.setStyleSheet(self.theme.CONTAINER_STYLE)
diff --git a/portprotonqt/system_overlay.py b/portprotonqt/system_overlay.py
new file mode 100644
index 0000000..a64b45e
--- /dev/null
+++ b/portprotonqt/system_overlay.py
@@ -0,0 +1,100 @@
+import subprocess
+from PySide6.QtWidgets import QDialog, QVBoxLayout, QPushButton, QLabel, QMessageBox
+from PySide6.QtWidgets import QApplication
+from PySide6.QtCore import Qt
+from portprotonqt.logger import get_logger
+from portprotonqt.localization import _
+
+logger = get_logger(__name__)
+
+class SystemOverlay(QDialog):
+    """Overlay dialog for system actions like reboot, sleep, shutdown, suspend, and exit."""
+    def __init__(self, parent, theme):
+        super().__init__(parent)
+        self.theme = theme
+        self.setWindowTitle(_("System Overlay"))
+        self.setModal(True)
+        self.setFixedSize(400, 300)
+
+        layout = QVBoxLayout(self)
+        layout.setContentsMargins(20, 20, 20, 20)
+        layout.setSpacing(10)
+
+        title = QLabel(_("System Actions"))
+        title.setStyleSheet(self.theme.TAB_TITLE_STYLE)
+        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
+        layout.addWidget(title)
+
+        # Reboot button
+        reboot_button = QPushButton(_("Reboot"))
+        #reboot_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        reboot_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        reboot_button.clicked.connect(self.reboot)
+        layout.addWidget(reboot_button)
+
+        # Shutdown button
+        shutdown_button = QPushButton(_("Shutdown"))
+        #shutdown_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        shutdown_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        shutdown_button.clicked.connect(self.shutdown)
+        layout.addWidget(shutdown_button)
+
+        # Suspend button
+        suspend_button = QPushButton(_("Suspend"))
+        #suspend_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        suspend_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        suspend_button.clicked.connect(self.suspend)
+        layout.addWidget(suspend_button)
+
+        # Exit application button
+        exit_button = QPushButton(_("Exit Application"))
+        #exit_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        exit_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        exit_button.clicked.connect(self.exit_application)
+        layout.addWidget(exit_button)
+
+        # Cancel button
+        cancel_button = QPushButton(_("Cancel"))
+        #cancel_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        cancel_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        cancel_button.clicked.connect(self.reject)
+        layout.addWidget(cancel_button)
+
+        # Set focus to the first button
+        reboot_button.setFocus()
+
+    def reboot(self):
+        try:
+            subprocess.run(["systemctl", "reboot"], check=True)
+        except subprocess.CalledProcessError as e:
+            logger.error(f"Failed to reboot: {e}")
+            QMessageBox.warning(self, _("Error"), _("Failed to reboot the system"))
+        self.accept()
+
+    def sleep(self):
+        try:
+            subprocess.run(["systemctl", "suspend-then-hibernate"], check=True)
+        except subprocess.CalledProcessError as e:
+            logger.error(f"Failed to sleep: {e}")
+            QMessageBox.warning(self, _("Error"), _("Failed to put the system to sleep"))
+        self.accept()
+
+    def shutdown(self):
+        try:
+            subprocess.run(["systemctl", "poweroff"], check=True)
+        except subprocess.CalledProcessError as e:
+            logger.error(f"Failed to shutdown: {e}")
+            QMessageBox.warning(self, _("Error"), _("Failed to shutdown the system"))
+        self.accept()
+
+    def suspend(self):
+        try:
+            subprocess.run(["systemctl", "suspend"], check=True)
+        except subprocess.CalledProcessError as e:
+            logger.error(f"Failed to suspend: {e}")
+            QMessageBox.warning(self, _("Error"), _("Failed to suspend the system"))
+        self.accept()
+
+    def exit_application(self):
+        QApplication.quit()
+        self.accept()

From 1ea5fd710c5303ea975148df52a66f7c4aef8d2b Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 09:07:18 +0500
Subject: [PATCH 12/47] feat: added --fullscreen cli argument

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/app.py | 15 ++++++++++++++-
 portprotonqt/cli.py | 16 ++++++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)
 create mode 100644 portprotonqt/cli.py

diff --git a/portprotonqt/app.py b/portprotonqt/app.py
index fdd2bbf..d215643 100644
--- a/portprotonqt/app.py
+++ b/portprotonqt/app.py
@@ -4,8 +4,9 @@ from PySide6.QtWidgets import QApplication
 from PySide6.QtGui import QIcon
 from portprotonqt.main_window import MainWindow
 from portprotonqt.tray import SystemTray
-from portprotonqt.config_utils import read_theme_from_config
+from portprotonqt.config_utils import read_theme_from_config, save_fullscreen_config
 from portprotonqt.logger import get_logger
+from portprotonqt.cli import parse_args
 
 logger = get_logger(__name__)
 
@@ -28,7 +29,17 @@ def main():
     else:
         logger.error(f"Qt translations for {system_locale.name()} not found in {translations_path}")
 
+    # Парсинг аргументов командной строки
+    args = parse_args()
+
     window = MainWindow()
+
+    # Обработка флага --fullscreen
+    if args.fullscreen:
+        logger.info("Запуск в полноэкранном режиме по флагу --fullscreen")
+        save_fullscreen_config(True)
+        window.showFullScreen()
+
     current_theme_name = read_theme_from_config()
     tray = SystemTray(app, current_theme_name)
     tray.show_action.triggered.connect(window.show)
@@ -43,7 +54,9 @@ def main():
         tray.hide_action.triggered.connect(window.hide)
 
     window.settings_saved.connect(recreate_tray)
+
     window.show()
+
     sys.exit(app.exec())
 
 if __name__ == '__main__':
diff --git a/portprotonqt/cli.py b/portprotonqt/cli.py
new file mode 100644
index 0000000..ed7d096
--- /dev/null
+++ b/portprotonqt/cli.py
@@ -0,0 +1,16 @@
+import argparse
+from portprotonqt.logger import get_logger
+
+logger = get_logger(__name__)
+
+def parse_args():
+    """
+    Парсит аргументы командной строки.
+    """
+    parser = argparse.ArgumentParser(description="PortProtonQT CLI")
+    parser.add_argument(
+        "--fullscreen",
+        action="store_true",
+        help="Запустить приложение в полноэкранном режиме и сохранить эту настройку"
+    )
+    return parser.parse_args()

From a21705da154b4cf896cb67a08cd6b8262164daac Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 09:11:52 +0500
Subject: [PATCH 13/47] feat: hide the games from EGS until after the workout

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/main_window.py | 88 +++----------------------------------
 1 file changed, 7 insertions(+), 81 deletions(-)

diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 90a5f6b..e5d1246 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -260,25 +260,19 @@ class MainWindow(QMainWindow):
                 self.update_status_message.emit
             )
         elif display_filter == "favorites":
-            def on_all_games(portproton_games, steam_games, epic_games):
-                games = [game for game in portproton_games + steam_games + epic_games if game[0] in favorites]
+            def on_all_games(portproton_games, steam_games):
+                games = [game for game in portproton_games + steam_games if game[0] in favorites]
                 self.games_loaded.emit(games)
             self._load_portproton_games_async(
                 lambda pg: self._load_steam_games_async(
-                    lambda sg: load_egs_games_async(
-                        self.legendary_path,
-                        lambda eg: on_all_games(pg, sg, eg),
-                        self.downloader,
-                        self.update_progress.emit,
-                        self.update_status_message.emit
-                    )
+                    lambda sg: on_all_games(pg, sg)
                 )
             )
         else:
-            def on_all_games(portproton_games, steam_games, epic_games):
+            def on_all_games(portproton_games, steam_games):
                 seen = set()
                 games = []
-                for game in portproton_games + steam_games + epic_games:
+                for game in portproton_games + steam_games:
                     name = game[0]
                     if name not in seen:
                         seen.add(name)
@@ -286,13 +280,7 @@ class MainWindow(QMainWindow):
                 self.games_loaded.emit(games)
             self._load_portproton_games_async(
                 lambda pg: self._load_steam_games_async(
-                    lambda sg: load_egs_games_async(
-                        self.legendary_path,
-                        lambda eg: on_all_games(pg, sg, eg),
-                        self.downloader,
-                        self.update_progress.emit,
-                        self.update_status_message.emit
-                    )
+                    lambda sg: on_all_games(pg, sg)
                 )
             )
         return []
@@ -915,7 +903,7 @@ class MainWindow(QMainWindow):
 
         # 3. Games display_filter
         self.filter_keys = ["all", "steam", "portproton", "favorites", "epic"]
-        self.filter_labels = [_("all"), "steam", "portproton", _("favorites"), "epic games store"]
+        self.filter_labels = [_("all"), "steam", "portproton", _("favorites")]
         self.gamesDisplayCombo = QComboBox()
         self.gamesDisplayCombo.addItems(self.filter_labels)
         self.gamesDisplayCombo.setStyleSheet(self.theme.SETTINGS_COMBO_STYLE)
@@ -984,37 +972,6 @@ class MainWindow(QMainWindow):
         self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen)
         formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox)
 
-        # 7. Legendary Authentication
-        self.legendaryAuthButton = AutoSizeButton(
-            _("Open Legendary Login"),
-            icon=self.theme_manager.get_icon("login")
-        )
-        self.legendaryAuthButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
-        self.legendaryAuthButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
-        self.legendaryAuthButton.clicked.connect(self.openLegendaryLogin)
-        self.legendaryAuthTitle = QLabel(_("Legendary Authentication:"))
-        self.legendaryAuthTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
-        self.legendaryAuthTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
-        formLayout.addRow(self.legendaryAuthTitle, self.legendaryAuthButton)
-
-        self.legendaryCodeEdit = QLineEdit()
-        self.legendaryCodeEdit.setPlaceholderText(_("Enter Legendary Authorization Code"))
-        self.legendaryCodeEdit.setStyleSheet(self.theme.PROXY_INPUT_STYLE)
-        self.legendaryCodeEdit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
-        self.legendaryCodeTitle = QLabel(_("Authorization Code:"))
-        self.legendaryCodeTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
-        self.legendaryCodeTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
-        formLayout.addRow(self.legendaryCodeTitle, self.legendaryCodeEdit)
-
-        self.submitCodeButton = AutoSizeButton(
-            _("Submit Code"),
-            icon=self.theme_manager.get_icon("save")
-        )
-        self.submitCodeButton.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
-        self.submitCodeButton.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
-        self.submitCodeButton.clicked.connect(self.submitLegendaryCode)
-        formLayout.addRow(QLabel(""), self.submitCodeButton)
-
         layout.addLayout(formLayout)
 
         # Кнопки
@@ -1065,37 +1022,6 @@ class MainWindow(QMainWindow):
             logger.error(f"Failed to open Legendary login page: {e}")
             self.statusBar().showMessage(_("Failed to open Legendary login page"), 3000)
 
-    def submitLegendaryCode(self):
-        """Submits the Legendary authorization code using the legendary CLI."""
-        auth_code = self.legendaryCodeEdit.text().strip()
-        if not auth_code:
-            QMessageBox.warning(self, _("Error"), _("Please enter an authorization code"))
-            return
-
-        try:
-            # Execute legendary auth command
-            result = subprocess.run(
-                [self.legendary_path, "auth", "--code", auth_code],
-                capture_output=True,
-                text=True,
-                check=True
-            )
-            logger.info("Legendary authentication successful: %s", result.stdout)
-            self.statusBar().showMessage(_("Successfully authenticated with Legendary"), 3000)
-            self.legendaryCodeEdit.clear()
-            # Reload Epic Games Store games after successful authentication
-            self.games = self.loadGames()
-            self.updateGameGrid()
-        except subprocess.CalledProcessError as e:
-            logger.error("Legendary authentication failed: %s", e.stderr)
-            self.statusBar().showMessage(_("Legendary authentication failed: {0}").format(e.stderr), 5000)
-        except FileNotFoundError:
-            logger.error("Legendary executable not found at %s", self.legendary_path)
-            self.statusBar().showMessage(_("Legendary executable not found"), 5000)
-        except Exception as e:
-            logger.error("Unexpected error during Legendary authentication: %s", str(e))
-            self.statusBar().showMessage(_("Unexpected error during authentication"), 5000)
-
     def resetSettings(self):
         """Сбрасывает настройки и перезапускает приложение."""
         reply = QMessageBox.question(

From 34e70d05f320d7ea440222eb728c1493cde8e4d1 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 09:20:53 +0500
Subject: [PATCH 14/47] feat: add continuous D-pad navigation

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 49 ++++++++++++++++++++++++-----------
 1 file changed, 34 insertions(+), 15 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index a9e29d5..4393236 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -4,7 +4,7 @@ from typing import Protocol, cast
 from evdev import InputDevice, ecodes, list_devices
 import pyudev
 from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView
-from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot
+from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer
 from PySide6.QtGui import QKeyEvent
 from portprotonqt.logger import get_logger
 from portprotonqt.image_utils import FullscreenDialog
@@ -72,7 +72,6 @@ class InputManager(QObject):
         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)
-
         self.axis_deadzone = axis_deadzone
         self.initial_axis_move_delay = initial_axis_move_delay
         self.repeat_axis_move_delay = repeat_axis_move_delay
@@ -84,6 +83,12 @@ class InputManager(QObject):
         self.running = True
         self._is_fullscreen = read_fullscreen_config()
 
+        # Add variables for continuous D-pad movement
+        self.dpad_timer = QTimer(self)
+        self.dpad_timer.timeout.connect(self.handle_dpad_repeat)
+        self.current_dpad_code = None  # Tracks the current D-pad axis (e.g., ABS_HAT0X, ABS_HAT0Y)
+        self.current_dpad_value = 0    # Tracks the current D-pad direction value (e.g., -1, 1)
+
         # Connect signals to slots
         self.button_pressed.connect(self.handle_button_slot)
         self.dpad_moved.connect(self.handle_dpad_slot)
@@ -239,6 +244,15 @@ class InputManager(QObject):
         except Exception as e:
             logger.error(f"Error in handle_button_slot: {e}", exc_info=True)
 
+    def handle_dpad_repeat(self) -> None:
+        """Handle repeated D-pad input while the D-pad is held."""
+        if self.current_dpad_code is not None and self.current_dpad_value != 0:
+            now = time.time()
+            if (now - self.last_move_time) >= self.current_axis_delay:
+                self.handle_dpad_slot(self.current_dpad_code, self.current_dpad_value, now)
+                self.last_move_time = now
+                self.current_axis_delay = self.repeat_axis_move_delay
+
     @Slot(int, int, float)
     def handle_dpad_slot(self, code: int, value: int, current_time: float) -> None:
         try:
@@ -253,6 +267,23 @@ class InputManager(QObject):
             focused = QApplication.focusWidget()
             popup = QApplication.activePopupWidget()
 
+            # Update D-pad state
+            if value != 0:
+                self.current_dpad_code = code
+                self.current_dpad_value = value
+                if not self.axis_moving:
+                    self.axis_moving = True
+                    self.last_move_time = current_time
+                    self.current_axis_delay = self.initial_axis_move_delay
+                    self.dpad_timer.start(int(self.repeat_axis_move_delay * 1000))  # Start timer (in milliseconds)
+            else:
+                self.current_dpad_code = None
+                self.current_dpad_value = 0
+                self.axis_moving = False
+                self.current_axis_delay = self.initial_axis_move_delay
+                self.dpad_timer.stop()  # Stop timer when D-pad is released
+                return
+
             # Handle SystemOverlay or AddGameDialog navigation with D-pad
             if isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0:
                 if not focused or not active.focusWidget():
@@ -307,19 +338,6 @@ class InputManager(QObject):
                     active.show_next()
                 return
 
-            # Handle repeated D-pad movement
-            if value != 0:
-                if not self.axis_moving:
-                    self.axis_moving = True
-                elif (current_time - self.last_move_time) < self.current_axis_delay:
-                    return
-                self.last_move_time = current_time
-                self.current_axis_delay = self.repeat_axis_move_delay
-            else:
-                self.axis_moving = False
-                self.current_axis_delay = self.initial_axis_move_delay
-                return
-
             # Library tab navigation (index 0)
             if self._parent.stackedWidget.currentIndex() == 0 and code in (ecodes.ABS_HAT0X, ecodes.ABS_HAT0Y):
                 focused = QApplication.focusWidget()
@@ -783,6 +801,7 @@ class InputManager(QObject):
     def cleanup(self) -> None:
         try:
             self.running = False
+            self.dpad_timer.stop()
             if self.gamepad_thread:
                 self.gamepad_thread.join()
             if self.gamepad:

From 14dc44d4f7e55a6b45ed39bdb4528433fadab1d0 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 09:22:51 +0500
Subject: [PATCH 15/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d352e6b..516b015 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,8 +7,6 @@
 
 ### Added
 - Кнопки сброса настроек и очистки кэша
-- Начальная интеграция с EGS с помощью [Legendary](https://github.com/derrod/legendary)
-- Бейдж EGS
 - Бейдж PortProton
 - Зависимость на `xdg-utils`
 - Интеграция статуса WeAntiCheatYet в карточку
@@ -38,9 +36,12 @@
 - Установка ширины бейджа в две трети ширины карточки
 - Бейджи источников (`Steam`, `EGS`, `PortProton`) теперь отображаются только при активном фильтре `all` или `favorites`
 - Карточки теперь фокусируются в направлении движения стрелок или D-pad, например если нажать D-pad вниз то перейдёшь на карточку со следующей колонки, а не по порядку
+- Теперь D-pad можно зажимать для переключения карточек
 - D-pad больше не переключает вкладки только RB и LB
 - Кнопка добавления игры больше не фокусируется
 - Диалог добавления игры теперь открывается только в библиотеке
+- Аргумент --fullscreen для открытия приложения в режиме полноэкранного отображения
+- Оверлей на кнопку Xbox / PS для закрытия приложения, выключения, перезагрузки и ухода в сон
 
 ### Fixed
 - Обработка несуществующей темы с возвратом к “standart”

From 647394ca92d3acb415f9e1da28baa586f3125159 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 09:34:24 +0500
Subject: [PATCH 16/47] chore(localization): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 documentation/localization_guide/README.md    |   6 +-
 documentation/localization_guide/README.ru.md |   6 +-
 .../locales/de_DE/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/de_DE/LC_MESSAGES/messages.po     |  60 ++++++++---------
 .../locales/es_ES/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/es_ES/LC_MESSAGES/messages.po     |  60 ++++++++---------
 portprotonqt/locales/messages.pot             |  60 ++++++++---------
 .../locales/ru_RU/LC_MESSAGES/messages.mo     | Bin 13281 -> 12956 bytes
 .../locales/ru_RU/LC_MESSAGES/messages.po     |  62 ++++++++----------
 portprotonqt/system_overlay.py                |  15 +----
 10 files changed, 120 insertions(+), 149 deletions(-)

diff --git a/documentation/localization_guide/README.md b/documentation/localization_guide/README.md
index 84fdcc5..d4b50af 100644
--- a/documentation/localization_guide/README.md
+++ b/documentation/localization_guide/README.md
@@ -20,9 +20,9 @@ Current translation status:
 
 | Locale | Progress | Translated |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 154 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 154 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 154 of 154 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 153 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 153 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 153 of 153 |
 
 ---
 
diff --git a/documentation/localization_guide/README.ru.md b/documentation/localization_guide/README.ru.md
index eb4ccca..1c4c7e0 100644
--- a/documentation/localization_guide/README.ru.md
+++ b/documentation/localization_guide/README.ru.md
@@ -20,9 +20,9 @@
 
 | Локаль | Прогресс | Переведено |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 154 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 154 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 154 из 154 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 153 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 153 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 153 из 153 |
 
 ---
 
diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo
index 0f3b2716b55ae08460c7354be3f460f4ddd4a5dc..71e89d71fd38dbc3f0b0bbe95dcf91bd71e501ba 100644
GIT binary patch
delta 17
YcmX@ie3*H{L^cZr14}F8jnj1)0W`%0R{#J2

delta 17
YcmX@ie3*H{L^d-8BLgdgjnj1)0W@<3O8@`>

diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
index 882d392..4145e5e 100644
--- a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-06 20:01+0500\n"
+"POT-Creation-Date: 2025-06-08 09:31+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: de_DE\n"
@@ -362,21 +362,6 @@ msgstr ""
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr ""
 
-msgid "Open Legendary Login"
-msgstr ""
-
-msgid "Legendary Authentication:"
-msgstr ""
-
-msgid "Enter Legendary Authorization Code"
-msgstr ""
-
-msgid "Authorization Code:"
-msgstr ""
-
-msgid "Submit Code"
-msgstr ""
-
 msgid "Save Settings"
 msgstr ""
 
@@ -392,22 +377,6 @@ msgstr ""
 msgid "Failed to open Legendary login page"
 msgstr ""
 
-msgid "Please enter an authorization code"
-msgstr ""
-
-msgid "Successfully authenticated with Legendary"
-msgstr ""
-
-#, python-brace-format
-msgid "Legendary authentication failed: {0}"
-msgstr ""
-
-msgid "Legendary executable not found"
-msgstr ""
-
-msgid "Unexpected error during authentication"
-msgstr ""
-
 msgid "Confirm Reset"
 msgstr ""
 
@@ -505,6 +474,33 @@ msgstr ""
 msgid "Launching"
 msgstr ""
 
+msgid "System Overlay"
+msgstr ""
+
+msgid "Reboot"
+msgstr ""
+
+msgid "Shutdown"
+msgstr ""
+
+msgid "Suspend"
+msgstr ""
+
+msgid "Exit Application"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Failed to reboot the system"
+msgstr ""
+
+msgid "Failed to shutdown the system"
+msgstr ""
+
+msgid "Failed to suspend the system"
+msgstr ""
+
 msgid "just now"
 msgstr ""
 
diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo
index 50770aafe487a049f1dcf75e6fbc437b043c3864..1c9c621316c433ce09621e87918dc6b8898ca4e6 100644
GIT binary patch
delta 17
YcmX@ie3*H{L^cZr14}F8jnj1)0W`%0R{#J2

delta 17
YcmX@ie3*H{L^d-8BLgdgjnj1)0W@<3O8@`>

diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
index b67ae5b..74cfe8c 100644
--- a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-06 20:01+0500\n"
+"POT-Creation-Date: 2025-06-08 09:31+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: es_ES\n"
@@ -362,21 +362,6 @@ msgstr ""
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr ""
 
-msgid "Open Legendary Login"
-msgstr ""
-
-msgid "Legendary Authentication:"
-msgstr ""
-
-msgid "Enter Legendary Authorization Code"
-msgstr ""
-
-msgid "Authorization Code:"
-msgstr ""
-
-msgid "Submit Code"
-msgstr ""
-
 msgid "Save Settings"
 msgstr ""
 
@@ -392,22 +377,6 @@ msgstr ""
 msgid "Failed to open Legendary login page"
 msgstr ""
 
-msgid "Please enter an authorization code"
-msgstr ""
-
-msgid "Successfully authenticated with Legendary"
-msgstr ""
-
-#, python-brace-format
-msgid "Legendary authentication failed: {0}"
-msgstr ""
-
-msgid "Legendary executable not found"
-msgstr ""
-
-msgid "Unexpected error during authentication"
-msgstr ""
-
 msgid "Confirm Reset"
 msgstr ""
 
@@ -505,6 +474,33 @@ msgstr ""
 msgid "Launching"
 msgstr ""
 
+msgid "System Overlay"
+msgstr ""
+
+msgid "Reboot"
+msgstr ""
+
+msgid "Shutdown"
+msgstr ""
+
+msgid "Suspend"
+msgstr ""
+
+msgid "Exit Application"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Failed to reboot the system"
+msgstr ""
+
+msgid "Failed to shutdown the system"
+msgstr ""
+
+msgid "Failed to suspend the system"
+msgstr ""
+
 msgid "just now"
 msgstr ""
 
diff --git a/portprotonqt/locales/messages.pot b/portprotonqt/locales/messages.pot
index 46314b4..14160cf 100644
--- a/portprotonqt/locales/messages.pot
+++ b/portprotonqt/locales/messages.pot
@@ -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-06-06 20:01+0500\n"
+"POT-Creation-Date: 2025-06-08 09: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"
@@ -360,21 +360,6 @@ msgstr ""
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr ""
 
-msgid "Open Legendary Login"
-msgstr ""
-
-msgid "Legendary Authentication:"
-msgstr ""
-
-msgid "Enter Legendary Authorization Code"
-msgstr ""
-
-msgid "Authorization Code:"
-msgstr ""
-
-msgid "Submit Code"
-msgstr ""
-
 msgid "Save Settings"
 msgstr ""
 
@@ -390,22 +375,6 @@ msgstr ""
 msgid "Failed to open Legendary login page"
 msgstr ""
 
-msgid "Please enter an authorization code"
-msgstr ""
-
-msgid "Successfully authenticated with Legendary"
-msgstr ""
-
-#, python-brace-format
-msgid "Legendary authentication failed: {0}"
-msgstr ""
-
-msgid "Legendary executable not found"
-msgstr ""
-
-msgid "Unexpected error during authentication"
-msgstr ""
-
 msgid "Confirm Reset"
 msgstr ""
 
@@ -503,6 +472,33 @@ msgstr ""
 msgid "Launching"
 msgstr ""
 
+msgid "System Overlay"
+msgstr ""
+
+msgid "Reboot"
+msgstr ""
+
+msgid "Shutdown"
+msgstr ""
+
+msgid "Suspend"
+msgstr ""
+
+msgid "Exit Application"
+msgstr ""
+
+msgid "Cancel"
+msgstr ""
+
+msgid "Failed to reboot the system"
+msgstr ""
+
+msgid "Failed to shutdown the system"
+msgstr ""
+
+msgid "Failed to suspend the system"
+msgstr ""
+
 msgid "just now"
 msgstr ""
 
diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo
index d774613691595af35eabde04fe679ac19651fe3d..6ce2539ba3f46b89cc5f313d42c068e26e5fbdbb 100644
GIT binary patch
delta 3126
zcmajgdrXye9LMqB0a5`4L6lUCN4y{*f|%wdGqcoLYD(U!<&9&K=s`GmD?0+27s#|n
zans7x)M>Y=M-dd1Hq(u4K4-0^%b6|zXzJ{?(kX4dKj%DV{n28_=k@zN&+qd6eSg2_
z@%xwF@dhp>$80nF?ciS;|E|ZVcJHq-)|ft2o3I`JfC+dV<M9r*$F^~i{wb*Y192RV
z#OH7=PQc697PD#8+n9hEL_rhGL=E&BDu!xogPUEqBXgPeumkRM@7G}-^(N$JZgA;@
zx3ME8FsRz6V?WG6Ens^1e!v(ng)ZDELru65`I#+T!V_Y6LbqOr_wl?LyWwfH@Oyj!
zqiC#&dtwIWV>(WAEyrHew_#7_Hw_f};<xUDD8f^RzL<?;F%wI%JMM7nbw~=$CAS`(
z$bi(du?J2>ZRsLZM%E)u%{!=ZKElBL6wXl4gqK|vzB*dSqRdES+sp)1K+`Y<E3h-x
zVlsY=+PafC7*C;vw^8GFPl`AKS*v+IiT!7rOc6IMEJv-V7M1$rsKfF(D&=3|5c~?Y
z(l}<-O8TR=a6D?|vr+9=yH+7x%m&ngccKD3oJ{^T!BuW(Pku#$HC@P;wj>i%aS|$^
z1<22oa8bY4U29Np$5vDZ-bH@q11_4d4wd?INRrJZ)Oa@n6qMRqZi8s@ss_oZ!<d0Q
zGFhm=Mxs_S2{qsxR4RR_fL6Qpwa8kGjcUKwy<d;Y&}mfvz!eIL{5RBut*8}5v$2Y>
z9ctiYREKUj1+%aKUqK7cq5A)fn)oKFUmW|QGn9;(n2ri~97gN?pXN50h1#<P7=^{C
zz<j7cR-gi_bf2$tpKnB+A=`bv%XK#@fc>a(4!ZY`;b{+t6w{gCyuk)_rNLgz!DiIy
zzv14G=i#%|`=AcnDqMlJ_#r0p5vayvs6fWjs1m1Q6@HESPRwFZZM7f6smJ!rZ#Gd-
zN_L|5dcPiE6VAXE)FI4Z;|AbxR4R*Hx8O+X4cHHFqRx=T*GM0vJT5vrlTl}34(hO%
zVnC7AQqZ0qL`{4OAI1<4!&qKker608y`J-NGOou*@w{uQ6=^>m^?WlvgO~9s9LPKS
zIIc#G+uW1<Yk=F_&<cj}y;R5LsQYJ74cic|d<-?fDpdO>WNvc@*-q1)=+rMCwUtY;
zKkmXu@GNFyY@bL52KNa>QZtJiOL_1zK8P2Q7s0gUBcN2Lp)&IX&ci9Fi4LRA#&0+V
z$J00$YfynT;?sB$M`ITI!;4`Sp~kNWP|)7j;#Ay?d3X~QNWZMe|BeMXkorp09)5&6
zL`_JN%voe{<|lj+Q|PR-RE9%w9TJ0S#)<eNGKC2YAs=cu8#Qs6Ti=aJX+3J9bEphl
zLe7`D<{C?WyHd|You$#J!}<d90vSK@GjDRymK;O1`!P}vm}u^Dz|BC^K$DQ|Gz-zf
z8dM;kAiH8tp;CJl6>uD{geDq++T&+Y?Y5u}^-)Z~7F5bFpys=QS$hBD*)YzZ8I1dI
z2JS(Pt_k*XX@}pS4&P<eK)u)x1u_(M7-ynZUW;w;Q&hVnt}Pf%{X0}(Kgfv`{-Dqv
zWAh^ox}dhC8)||9*cK<DGBF*KaWSgh8r1Wx`N5XhS3PzhzNh_4{JiM--s0ee`1?Gz
zr^B3%Pp&BPTcb-#ii_sY^%wb;1m|=p_ax5q`TUlDq1P%~S?2dHwtr4Yv#%tqwnrt#
z<t{Au7y6bj`Nv~3c5YFzx6tzYtg`a567Ld<fA<Z}P8<~#Y)$fd>}8!+*hwiBF&f=|
zE9LA%?og(nsCb#T)D-xAC8nUfq{LV1_ZCtLk7WwNF<6snQapF1y{Ge);DIiwo;E|R
zykWx!*<Yq+xW(Y_sZlX@OV_FPwX|G&d)n3b_d=CUqjSV*at;IoJz72Xq4Zq|Z-=U#
z1|HN~PQ7!~{w1@s{aHrmV4-y|D$6<P90{$V$5H2i6AY~hRZ*%BRfcM;Pz8kw`W<l^
zLsfLiv2%Kzx4-DsX+XI3A@vNczuS5Z0~~Zt(6#E{U8^<Qy@}jN4p~l}b2wBL+CWeZ
zRyc+_ij8)z)jOEkyD%o$JF7Ix{wn*Hosjcq*IoY=XE1g^ohM0aJmwq^Z3?Z61Y|em
w_O*xR^_XzC<NwWTocHgo*J)DDEa!w1UZr!KBsAEqc^Sd-yaS%#;(Wj7FVkTtO#lD@

delta 3517
zcma*n4Qy5A9mnzK)`HT~ciMt<^iYcy3$;K5EJAEiVc1S85*4-XwmmI3srS~~+lfrP
zg|dyQiPaMZOqgT1jVu|ooE8fWWl@;{G&^_m1vg*fqSN>iHK<XB#_#WPmJ6C0o7?+&
z&N<KX{QuAYdCuK#ztJ9lEkAv;;qOuYOZfjJU012UXU7>cjmzio0z8S?cp5V?GsBqi
zSd1E9j=EoqORxb~;bvThAL049fKCO*#LZ$F<=nUl73f~ngug@OvKPnVpzjD0+dPR`
z_$*fAKar2gWH1w(V$8)#RKF{+0+*o{(3QL&H)aEkB5w4d0{s#Bm_2+c&<JXRBmVV2
zu#D?haSZC=g)kN^oQhMh4%IJ+rMS`ealDA@r*SIr%{w$M#thQZgITBnE3p#0@e<tW
zdlXf%kNxYS946v=5o+Z>Mjn|MPR3rGh>xM>9riteaqZdXG+2x&q+V=~DMJOGi4*V^
zEW|M8<3`lhY{AQMJ6ia6RDhGd1$k#GHwU#fwWxj#s0D=c*ngGS;l?D~;Xil+Rl-+L
zhvmPhQlCfkxi}8B;;T?AY({P2efR_1jOy?D9zdorhf$SCpb~#KpZY6+Mcq{T3M6?`
zk6QWdsI7@&F>XU8Jb--62wxiiH{X{~Z^`SZ3cZ6m10NtClgp$^pd7VD3*$7XzNtq|
zd>d*D+WZc8`OkY$6ZD}Hd>pl+A=E_AAjinOh)U>9|N4DovE~%2e-`_wex;~N#b?sc
z1lOTX|0-1GLDZhL;dG3m65NiB_y=5ppQDBK)L-N8L<NkYD)$RiLVHkWY(HLtPa%oK
z&3iP?<Hi?$hc8ikmqESLFc($Q0#pJes01(epI7?Nt56Bg^PeyFU5ZMe0X5G`|9%tx
zHNgJ2(9o&9loO~!xQwrv*ot~P9`Wz*#T&UkiaK;tc<I+*4gLvTd;ptxs6^gH9pcY$
z0~Rt_FK$KsUX)P(4C0%4G_(bEs1h}z_NWQ9=N)(x_8_^M6Ig{Gp-Ndw6uAT!Vg&U|
z_h;1EdJ*}U|L~==GltdbEEHipdFp8>qk7cdwc}h|i%Mt!=iw+`fuA8CQ*9Y@Jub&Z
z_*>uCQT?Xyj_Uc%xEy!mb@&C=;WcIKzdCN>9nl0&q6P%WO4p63=Y77%P=V9=_28J8
za#X(ss4ZKIGjKOvi7#L!=1)8GdM`p%pc(JNu4!@hoyJ?-;0-ak99fle8fxVY*o>=C
zfexY0z-e5JjdZ>YH=`0eg7x?o*5YjTffvNYkX+3Ds7mg|1^9NHh5}#2JElFKk2m18
zSdH6I2_8doG@s%uoXpKUY(SlzJCNd;Fe=c)*o6C0XQ+@e%*W|SZl)Pkq4@nY8fZL&
z<YEe`p9U;KC2)g(-HR&u7F57JI35pS9Ueo@qsd|)WF=0*AEM4y3+nLRhx!Hl6*44l
z_R`>EUh&P~X!C}c`KSq2BHL~vXrY5jWDr%6<EYYqj7m6<{ZN29)CzA!_1lWsIEt$H
zb2y3k=HE1GxN#a4cpkIt#vAeX_$6xMUDW*od>*ypS8)kiv(HSp8kNX8)N8l{wc>x_
zSUiaee9AXx4nKUibEBL_K34cHK$Z4K>VUUm0p5WM(1qutgQ~=%sPX%-0FR)azdXl1
zIPTs+&gw`sv^E$EMZ(tNNUQD4%G~7sB{LA18jjgftKM$6!>z&SJyvaZ?3-N=k8gHX
zW)-<hvzh`<PR<zT!|V(vk-g6SWzNF1+s+wjhhw3ZWRx0{nls3psV7!jFw|kU)>uES
zUUya}dyU=F9Sb&h*j6|av)Urv;a2zQy!JrxS=rS`+CyP;eWx9^Tb<nOLZ_}EIJLtL
zcG(sISV%BzDOzf=En4i(f?lU`!qH`+j-S|3vmzGhG%LEB?+nF~wV>V7Vs~}5b$4{!
zlN|0RYqwf=hhnQ!@#)#w8HvUSUQzgFxxJ>-ZsEeRqtQszYVD4O!tG~IK(*a*MHdFf
z&b4M&*Hk;xi_83`+gO~IUh->i)El(Ces5o5gEyM!@rJ!8y#B<`aoAlxIUI10mz)Y*
z{)o5F8}bIdVR{W&-oft}mCDt5YRXP$qE+B-FC9p8vdbnqyUI>DSC?9DNqJj(yzkpF
z6AxM5zSIOY#@p!)5bFRLCVCayJH+>JVk7w-{9m5uj!AB+w?k<jVqFKwno$;m)_aGU
zYlJBt;+8X`Dmx<;%k8O%rBx>$w7dbTKs4$wl<2V-K9Wo;@iQ&j>pv%&`}U<{0=19v
zWIwS+7<C}Ao(%hE(Cz!z%;~F|;Cxm2o|`^%FyI`oDqry%COND`n1rG$uVm4Uw=21h
zp~NOFPnrGS%J49;KD9#UgQ`*|P;I$uF6$4}DdS<ShiUwYMwxS%dQ(^{8GQIVCg*sR
I%~~7y8v8)rb^rhX

diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
index 3fe79d9..d555b37 100644
--- a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
@@ -9,8 +9,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-06 20:01+0500\n"
-"PO-Revision-Date: 2025-06-06 20:01+0500\n"
+"POT-Creation-Date: 2025-06-08 09:31+0500\n"
+"PO-Revision-Date: 2025-06-08 09:31+0500\n"
 "Last-Translator: \n"
 "Language: ru_RU\n"
 "Language-Team: ru_RU <LL@li.org>\n"
@@ -369,21 +369,6 @@ msgstr "Режим полноэкранного отображения прил
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr "Режим полноэкранного отображения приложения при подключении геймпада:"
 
-msgid "Open Legendary Login"
-msgstr "Открыть браузер для входа в Legendary"
-
-msgid "Legendary Authentication:"
-msgstr "Авторизация в Legendary:"
-
-msgid "Enter Legendary Authorization Code"
-msgstr "Введите код авторизации Legendary"
-
-msgid "Authorization Code:"
-msgstr "Код авторизации:"
-
-msgid "Submit Code"
-msgstr "Отправить код"
-
 msgid "Save Settings"
 msgstr "Сохранить настройки"
 
@@ -399,22 +384,6 @@ msgstr "Открытие страницы входа в Legendary в брауз
 msgid "Failed to open Legendary login page"
 msgstr "Не удалось открыть страницу входа в Legendary"
 
-msgid "Please enter an authorization code"
-msgstr "Пожалуйста, введите код авторизации"
-
-msgid "Successfully authenticated with Legendary"
-msgstr "Успешная аутентификация с Legendary"
-
-#, python-brace-format
-msgid "Legendary authentication failed: {0}"
-msgstr "Сбой аутентификации в Legendary: {0}"
-
-msgid "Legendary executable not found"
-msgstr "Не найден исполняемый файл Legendary"
-
-msgid "Unexpected error during authentication"
-msgstr "Неожиданная ошибка при аутентификации"
-
 msgid "Confirm Reset"
 msgstr "Подтвердите удаление"
 
@@ -514,6 +483,33 @@ msgstr "Невозможно запустить игру пока запущен
 msgid "Launching"
 msgstr "Идёт запуск"
 
+msgid "System Overlay"
+msgstr "Системный оверлей"
+
+msgid "Reboot"
+msgstr "Перезагрузить"
+
+msgid "Shutdown"
+msgstr "Выключить"
+
+msgid "Suspend"
+msgstr "Перейти в ждущий режим"
+
+msgid "Exit Application"
+msgstr "Выйти из приложения"
+
+msgid "Cancel"
+msgstr "Отмена"
+
+msgid "Failed to reboot the system"
+msgstr "Не удалось перезагрузить систему"
+
+msgid "Failed to shutdown the system"
+msgstr "Не удалось завершить работу системы"
+
+msgid "Failed to suspend the system"
+msgstr "Не удалось перейти в ждущий режим"
+
 msgid "just now"
 msgstr "только что"
 
diff --git a/portprotonqt/system_overlay.py b/portprotonqt/system_overlay.py
index a64b45e..f62bb10 100644
--- a/portprotonqt/system_overlay.py
+++ b/portprotonqt/system_overlay.py
@@ -1,5 +1,5 @@
 import subprocess
-from PySide6.QtWidgets import QDialog, QVBoxLayout, QPushButton, QLabel, QMessageBox
+from PySide6.QtWidgets import QDialog, QVBoxLayout, QPushButton, QMessageBox
 from PySide6.QtWidgets import QApplication
 from PySide6.QtCore import Qt
 from portprotonqt.logger import get_logger
@@ -20,11 +20,6 @@ class SystemOverlay(QDialog):
         layout.setContentsMargins(20, 20, 20, 20)
         layout.setSpacing(10)
 
-        title = QLabel(_("System Actions"))
-        title.setStyleSheet(self.theme.TAB_TITLE_STYLE)
-        title.setAlignment(Qt.AlignmentFlag.AlignCenter)
-        layout.addWidget(title)
-
         # Reboot button
         reboot_button = QPushButton(_("Reboot"))
         #reboot_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
@@ -71,14 +66,6 @@ class SystemOverlay(QDialog):
             QMessageBox.warning(self, _("Error"), _("Failed to reboot the system"))
         self.accept()
 
-    def sleep(self):
-        try:
-            subprocess.run(["systemctl", "suspend-then-hibernate"], check=True)
-        except subprocess.CalledProcessError as e:
-            logger.error(f"Failed to sleep: {e}")
-            QMessageBox.warning(self, _("Error"), _("Failed to put the system to sleep"))
-        self.accept()
-
     def shutdown(self):
         try:
             subprocess.run(["systemctl", "poweroff"], check=True)

From 08f4a0215bd8ee295ef03ce1ae70f9d180f47818 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 15:02:03 +0500
Subject: [PATCH 17/47] feat: added ecodes.KEY_HOMEPAGE (PS button) for overlay
 open

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 4393236..1b4fa7a 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -44,7 +44,7 @@ BUTTONS = {
     'confirm_stick': {ecodes.BTN_THUMBL, ecodes.BTN_THUMBR},
     'context_menu': {ecodes.BTN_START},
     'menu':      {ecodes.BTN_SELECT},
-    'guide':     {ecodes.BTN_MODE},
+    'guide':     {ecodes.BTN_MODE, ecodes.KEY_HOMEPAGE},
 }
 
 class InputManager(QObject):

From 23bcae32d26e663bfaf640850c0156b0efa63f8d Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 19:36:44 +0500
Subject: [PATCH 18/47] chore(changelog): update with grammar and typo fixes

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 61 ++++++++++++++++++++++++++--------------------------
 1 file changed, 30 insertions(+), 31 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 516b015..820b8bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,52 +8,52 @@
 ### Added
 - Кнопки сброса настроек и очистки кэша
 - Бейдж PortProton
-- Зависимость на `xdg-utils`
+- Зависимость от `xdg-utils`
 - Интеграция статуса WeAntiCheatYet в карточку
-- Стили в  AddGameDialog
-- Переключение полноэкранного режима через F11 или Select на геймпаде
-- Выбор QCheckBox через Enter или кнопку A геймпада
-- Закрытие диалога добавления игры через ESC или кнопку B геймпада
+- Стили в AddGameDialog
+- Переключение полноэкранного режима через F11 или кнопку Select на геймпаде
+- Выбор QCheckBox через Enter или кнопку A на геймпаде
+- Закрытие диалога добавления игры через ESC или кнопку B на геймпаде
 - Закрытие окна приложения по комбинации клавиш Ctrl+Q
-- Сохранение и восстановление размера при рестарте
+- Сохранение и восстановление размера окна при перезапуске
 - Переключатель полноэкранного режима приложения
-- Пункт в контекстное меню “Открыть папку игры”
-- Пункт в контекстное меню “Добавить в Steam”
-- Пункт в контекстное меню "Удалить из Steam”
-- Метод сортировки сначала избранное
-- Настройка автоматического перехода в режим полноэкранного отображения приложения при подключении геймпада (по умолчанию отключено)
-- Обработчики для QMenu и QComboBox на геймпаде
+- Пункт в контекстном меню «Открыть папку игры»
+- Пункт в контекстном меню «Добавить в Steam»
+- Пункт в контекстном меню «Удалить из Steam»
+- Метод сортировки «Сначала избранное»
+- Настройка автоматического перехода в полноэкранный режим при подключении геймпада (по умолчанию отключена)
+- Обработчики для QMenu и QComboBox при управлении геймпадом
+- Аргумент `--fullscreen` для запуска приложения в полноэкранном режиме
+- Оверлей на кнопку Xbox/PS для закрытия приложения, выключения, перезагрузки и перехода в спящий режим
 
 ### Changed
 - Обновлены все иконки
-- Переименован `_get_steam_home` → `get_steam_home`
-- Переименован `steam_game` → `game_source`
-- Догика контекстного меню вынесена в `ContextMenuManager`
+- Переименована функция `_get_steam_home` в `get_steam_home`
+- Переименован `steam_game` в `game_source`
+- Логика контекстного меню вынесена в `ContextMenuManager`
 - Бейдж Steam теперь открывает Steam Community
 - Изменена лицензия с MIT на GPL-3.0 для совместимости с кодом от legendary
-- Оптимизирована генерация карточек для предотвращения лагов при поиске и изменения размера окна
-- Бейджи с карточек так же теперь дублируются и на странице с деталями, а не только в библиотеке
-- Установка ширины бейджа в две трети ширины карточки
+- Оптимизирована генерация карточек для предотвращения задержек при поиске и изменении размера окна
+- Бейджи с карточек теперь отображаются также на странице с деталями, а не только в библиотеке
+- Установлена ширина бейджа в две трети ширины карточки
 - Бейджи источников (`Steam`, `EGS`, `PortProton`) теперь отображаются только при активном фильтре `all` или `favorites`
-- Карточки теперь фокусируются в направлении движения стрелок или D-pad, например если нажать D-pad вниз то перейдёшь на карточку со следующей колонки, а не по порядку
+- Карточки теперь фокусируются в направлении движения стрелок или D-pad: например, при нажатии D-pad вниз фокус переходит на карточку в следующей колонке, а не по порядку
 - Теперь D-pad можно зажимать для переключения карточек
-- D-pad больше не переключает вкладки только RB и LB
+- D-pad больше не переключает вкладки, только RB и LB
 - Кнопка добавления игры больше не фокусируется
 - Диалог добавления игры теперь открывается только в библиотеке
-- Аргумент --fullscreen для открытия приложения в режиме полноэкранного отображения
-- Оверлей на кнопку Xbox / PS для закрытия приложения, выключения, перезагрузки и ухода в сон
 
 ### Fixed
-- Обработка несуществующей темы с возвратом к “standart”
+- Обработка несуществующей темы с возвратом к «standard»
 - Открытие контекстного меню
 - Запуск при отсутствии exiftool
 - Переводы пунктов настроек
-- Бесконечное обращение к get_portproton_location
+- Бесконечное обращение к `get_portproton_location`
 - Ссылки на документацию в README
-- traceback при загрузке placeholder при отсутствии обложек
+- Traceback при загрузке placeholder при отсутствии обложек
 - Утечки памяти при загрузке обложек
 - Ошибки при подключении геймпада из-за работы в разных потоках
-- Множественное открытие диалога добавления игры на геймпаде
+- Многократное открытие диалога добавления игры при использовании геймпада
 - Перехват событий геймпада во время работы игры
 
 ---
@@ -67,16 +67,15 @@
 - Сборка AppImage
 
 ### Changed
-- Удалён жёстко заданный ресайз окна
-- Использован icoextract как python модуль
+- Удалён жёстко заданный размер окна
+- Использован `icoextract` как Python-модуль
 
 ### Fixed
 - Скрытие статус-бара
 - Чтение списка Steam-игр
-- Подвисание GUI
-- Краш при повреждённом Steam
+- Зависание GUI
+- Сбой при повреждённом Steam
 
 ---
 
-
 > См. подробности по каждому коммиту в истории репозитория.

From e1d7bca05e25a5d427d22fd6231d7d8f3ea446ad Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 19:54:04 +0500
Subject: [PATCH 19/47] chore(readme): update with grammar and typo fixes

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 README.md | 115 +++++++++++++++++++++++++++++-------------------------
 1 file changed, 62 insertions(+), 53 deletions(-)

diff --git a/README.md b/README.md
index 598d136..aa5f634 100644
--- a/README.md
+++ b/README.md
@@ -1,67 +1,70 @@
 <div align="center">
-  <img src="https://raw.githubusercontent.com/Castro-Fidel/PortWINE/master/data_from_portwine/img/gui/portproton.svg" width="64">
+  <img src="https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/portprotonqt/themes/standart/images/theme_logo.svg" width="64">
   <h1 align="center">PortProtonQt</h1>
-  <p align="center">Современный, удобный графический интерфейс, написанный с использованием PySide6(Qt6) и предназначенный для упрощения управления и запуска игр на различных платформах, включая PortProton, Steam и Epic Games Store.</p>
+  <p align="center">Удобный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store. Оно объединяет библиотеки игр в единый центр для лёгкой навигации и организации. Лёгкая структура и кроссплатформенная поддержка обеспечивают цельный игровой опыт без необходимости использования нескольких лаунчеров. Интеграция с PortProton упрощает запуск Windows-игр на Linux с минимальной настройкой.</p>
 </div>
 
+
 ## В планах
 
 - [X] Адаптировать структуру проекта для поддержки инструментов сборки
-- [X] Добавить возможность управление с геймпада
-- [ ] Добавить возможность управление с тачскрина
-- [X] Добавить возможность управление с мыши и клавиатуры
+- [X] Добавить возможность управления с геймпада
+- [ ] Добавить возможность управления с тачскрина
+- [X] Добавить возможность управления с мыши и клавиатуры
 - [X] Добавить систему тем [Документация](documentation/theme_guide)
-- [X] Вынести все константы такие как уровень закругления карточек в темы (Частично вынесено)
-- [X] Добавить метадату для тем (скришоты, описание, домащняя страница и автор)
-- [ ] Продумать систему вкладок вместо той что есть сейчас
-- [ ] Добавить Gamescope сессию на подобие той что есть в SteamOS
-- [ ] Написать адаптивный дизайн (За эталон берём SteamDeck с разрешением 1280х800)
-- [ ] Переделать скриншоты для соответсвия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
-- [X] Брать описание и названия игр с базы данных Steam
-- [X] Брать обложки для игр со SteamGridDB или CDN Steam
-- [X] Оптимизировать работу со SteamApi что бы ускорить время запуска
-- [X] Улучшить функцию поиска SteamApi что бы исправить некорректное определение ID (Graven определается как ENGRAVEN или GRAVENFALL, Spore определается как SporeBound или Spore Valley)
-- [ ] Убрать логи со SteamApi в релизной версии потому что логи замедляют код
-- [X] Что-то придумать с ограничением SteamApi в 50 тысяч игр за один запрос (иногда туда не попадают нужные игры и остаются без обложки)
-- [X] Избавится от любого вызова yad
-- [X] Написать свою реализацию запрета ухода в сон, а не использовать ту что в PortProton (Оставим это [PortProton 2.0](https://github.com/Castro-Fidel/PortProton_2.0))
-- [X] Написать свою реализацию трея, а не использовать ту что в PortProton
-- [X] Добавить в поиск экранную клавиатуру (Реализовавывать собственную клавиатуру слишком затратно, лучше положится на встроенную в DE клавиатуру malit в KDE, gjs-osk в GNOME,Squeekboard в phosh, стимовская в SteamOS и так далее)
-- [X] Добавить сортировку карточек по различным критериям (сейчас есть: недавние, кол-во наиграного времени, избранное или по алфавиту)
+- [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
+- [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор)
+- [ ] Продумать систему вкладок вместо текущей
+- [ ] Добавить сессию Gamescope, аналогичную той, что используется в SteamOS
+- [ ] Разработать адаптивный дизайн (за эталон берётся 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] Оптимизировать работу со Steam API для ускорения времени запуска
+- [X] Улучшить функцию поиска в Steam API для исправления некорректного определения ID (например, Graven определялся как ENGRAVEN или GRAVENFALL, Spore — как SporeBound или Spore Valley)
+- [ ] Убрать логи Steam API в релизной версии, так как они замедляют выполнение кода
+- [X] Решить проблему с ограничением Steam API в 50 тысяч игр за один запрос (иногда нужные игры не попадают в выборку и остаются без обложки)
+- [X] Избавиться от вызовов yad
+- [X] Реализовать собственный механизм запрета ухода в спящий режим вместо использования механизма PortProton (оставлено для [PortProton 2.0](https://github.com/Castro-Fidel/PortProton_2.0))
+- [X] Реализовать собственный системный трей вместо использования трея PortProton
+- [X] Добавить экранную клавиатуру в поиск (реализация собственной клавиатуры слишком затратна, поэтому используется встроенная в DE клавиатура: Maliit в KDE, gjs-osk в GNOME, Squeekboard в Phosh, клавиатура SteamOS и т.д.)
+- [X] Добавить сортировку карточек по различным критериям (доступны: по недавности, количеству наигранного времени, избранному или алфавиту)
 - [X] Добавить индикацию запуска приложения
-- [X] Достичь паритета функционала с Ingame
-- [ ] Достичь паритета функционала с PortProton
-- [X] Добавить возможность изменения названия, описания и обложки через файлы .local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}
-- [X] Добавить встроенное переопределение имени, описания и обложки, например по пути portprotonqt/custom_data [Документация](documentation/metadata_override/)
-- [X] Добавить в карточку игры сведения о поддержке геймадов
+- [X] Достигнуть паритета функциональности с Ingame
+- [ ] Достигнуть паритета функциональности с PortProton
+- [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}`
+- [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/)
+- [X] Добавить в карточку игры сведения о поддержке геймпада
 - [X] Добавить в карточки данные с ProtonDB
-- [X] Добавить в карточки данные с Are We Anti-Cheat Yet?
-- [X] Продублировать бейджы с карточки на страницу с деталями игрыы
-- [X] Добавить парсинг ярлыков со Steam
-- [X] Добавить парсинг ярлыков с EGS
-- [ ] Избавится от бинарника legendary
-- [ ] Добавить запуск и скачивание игр с EGS
-- [ ] Добавить авторизацию в EGS через WebView, а не вручную
-- [X] Брать описания для игр с EGS из их [api](https://store-content.ak.epicgames.com/api)
-- [X] Брать slug через Graphql [запрос](https://launcher.store.epicgames.com/graphql)
-- [X] Добавить на карточку бейдж того что игра со стима
-- [X] Добавить поддержку Flatpak и Snap версии Steam
-- [X] Выводить данные о самом недавнем пользователе Steam, а не первом попавшемся
-- [X] Исправить склонения в детальном выводе времени, например не 3 часов назад, а 3 часа назад
+- [X] Добавить в карточки данные с AreWeAntiCheatYet
+- [X] Продублировать бейджи с карточки на страницу с деталями игры
+- [X] Добавить парсинг ярлыков из Steam
+- [X] Добавить парсинг ярлыков из EGS (скрыто для переработки)
+- [ ] Избавиться от бинарника legendary
+- [ ] Добавить запуск и скачивание игр из EGS
+- [ ] Добавить авторизацию в EGS через WebView вместо ручного ввода
+- [X] Получать описания для игр из EGS через их [API](https://store-content.ak.epicgames.com/api)
+- [X] Получать slug через GraphQL [запрос](https://launcher.store.epicgames.com/graphql)
+- [X] Добавить на карточку бейдж, указывающий, что игра из Steam
+- [X] Добавить поддержку версий Steam для Flatpak и Snap
+- [X] Отображать данные о самом последнем пользователе Steam, а не первом попавшемся
+- [X] Исправить склонения в детальном выводе времени, например, не «3 часов назад», а «3 часа назад»
 - [X] Добавить перевод через gettext [Документация](documentation/localization_guide)
-- [X] Писать описание игр и прочие данные на языке системы
-- [X] Добавить недокументированные параметры конфигурации в GUI (time detail_level, games sort_method, games display_filter)
-- [X] Добавить систему избранного к карточкам
-- [X] Заменить все print на logging
-- [ ] Привести все логи к одному языку
-- [X] Стилизовать все элементы без стилей(QMessageBox, QSlider, QDialog)
-- [X] Убрать жёсткую привязку путей на стрелочки QComboBox в styles.py
+- [X] Отображать описания игр и другие данные на языке системы
+- [X] Добавить недокументированные параметры конфигурации в GUI (time_detail_level, games_sort_method, games_display_filter)
+- [X] Добавить систему избранного для карточек
+- [X] Заменить все `print` на `logging`
+- [ ] Привести все логи к единому языку
+- [X] Стилизовать все элементы без стилей (QMessageBox, QSlider, QDialog)
+- [X] Убрать жёсткую привязку путей к стрелочкам QComboBox в `styles.py`
 - [X] Исправить частичное применение тем на лету
-- [X] Исправить наложение подписей скриншотов при первом перелистывание в полноэкранном режиме
-- [ ] Добавить GOG (?)
-- [ ] Определится уже наконец с названием (PortProtonQt или PortProtonQT)
+- [X] Исправить наложение подписей скриншотов при первом перелистывании в полноэкранном режиме
+- [ ] Добавить поддержку GOG (?)
+- [ ] Определиться с названием (PortProtonQt или PortProtonQT или вообще третий вариант)
+- [ ] Добавить данные с HowLongToBeat на страницу с деталями игры (?)
+- [ ] Добавить виброотдачу на геймпаде при запуске игры (?)
 
-### Установка (debug)
+### Установка (devel)
 
 ```sh
 uv python install 3.10
@@ -71,6 +74,12 @@ source .venv/bin/activate
 
 Запуск производится по команде portprotonqt
 
+### Установка (release)
+
+Выберите подходящий пакет для вашей системы или AppImage.
+
+Запуск производится по команде portprotonqt или по ярлыку в меню
+
 ### Разработка
 
 В проект встроен линтер (ruff), статический анализатор (pyright) и проверка lock файла, если эти проверки не пройдут PR не будет принят, поэтому перед коммитом введите такую команду
@@ -90,9 +99,9 @@ pre-commit run --all-files
 
 ## Авторы
 
-* [Boria138](https://github.com/Boria138) - Программист
+* [Boria138](https://git.linux-gaming.ru/Boria138) - Программист
 * [BlackSnaker](https://github.com/BlackSnaker) - Дизайнер - программист
-* [Mikhail Tergoev(Castro-Fidel)](https://github.com/Castro-Fidel) - Автор оригинального проекта PortProton
+* [Mikhail Tergoev(Castro-Fidel)](https://git.linux-gaming.ru/CastroFidel) - Автор оригинального проекта PortProton
 
 > [!WARNING]
 > Проект находится на стадии WIP (work in progress) корректная работоспособность не гарантирована

From b965b23a5069a5fc95ad3f54e5db94506826b2b8 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 22:53:16 +0500
Subject: [PATCH 20/47] feat: add toggle favorite actions to context menu

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/context_menu_manager.py | 33 +++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py
index a524c05..2918d1e 100644
--- a/portprotonqt/context_menu_manager.py
+++ b/portprotonqt/context_menu_manager.py
@@ -6,7 +6,7 @@ import subprocess
 from PySide6.QtWidgets import QMessageBox, QDialog, QMenu
 from PySide6.QtCore import QUrl, QPoint
 from PySide6.QtGui import QDesktopServices
-from portprotonqt.config_utils import parse_desktop_entry
+from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites
 from portprotonqt.localization import _
 from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam
 from portprotonqt.dialogs import AddGameDialog
@@ -41,6 +41,17 @@ class ContextMenuManager:
         """
 
         menu = QMenu(self.parent)
+
+        favorites = read_favorites()
+        is_favorite = game_card.name in favorites
+
+        if is_favorite:
+            favorite_action = menu.addAction(_("Remove from Favorites"))
+            favorite_action.triggered.connect(lambda: self.toggle_favorite(game_card, False))
+        else:
+            favorite_action = menu.addAction(_("Add to Favorites"))
+            favorite_action.triggered.connect(lambda: self.toggle_favorite(game_card, True))
+
         if game_card.game_source not in ("steam", "epic"):
             desktop_dir = subprocess.check_output(['xdg-user-dir', 'DESKTOP']).decode('utf-8').strip()
             desktop_path = os.path.join(desktop_dir, f"{game_card.name}.desktop")
@@ -80,6 +91,26 @@ class ContextMenuManager:
 
         menu.exec(game_card.mapToGlobal(pos))
 
+    def toggle_favorite(self, game_card, add: bool):
+        """
+        Toggle the favorite status of a game and update its icon.
+
+        Args:
+            game_card: The GameCard instance to toggle.
+            add: True to add to favorites, False to remove.
+        """
+        favorites = read_favorites()
+        if add and game_card.name not in favorites:
+            favorites.append(game_card.name)
+            game_card.is_favorite = True
+            self.parent.statusBar().showMessage(_("Added '{0}' to favorites").format(game_card.name), 3000)
+        elif not add and game_card.name in favorites:
+            favorites.remove(game_card.name)
+            game_card.is_favorite = False
+            self.parent.statusBar().showMessage(_("Removed '{0}' from favorites").format(game_card.name), 3000)
+        save_favorites(favorites)
+        game_card.update_favorite_icon()
+
     def _check_portproton(self):
         """Check if PortProton is available."""
         if self.portproton_location is None:

From 55c32457d6b6770e83a7a4b93a2d12a5bb85a2b7 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 22:56:19 +0500
Subject: [PATCH 21/47] chore(localization): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 documentation/localization_guide/README.md    |   6 +++---
 documentation/localization_guide/README.ru.md |   6 +++---
 .../locales/de_DE/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/de_DE/LC_MESSAGES/messages.po     |  16 +++++++++++++++-
 .../locales/es_ES/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/es_ES/LC_MESSAGES/messages.po     |  16 +++++++++++++++-
 portprotonqt/locales/messages.pot             |  16 +++++++++++++++-
 .../locales/ru_RU/LC_MESSAGES/messages.mo     | Bin 12956 -> 13291 bytes
 .../locales/ru_RU/LC_MESSAGES/messages.po     |  18 ++++++++++++++++--
 9 files changed, 67 insertions(+), 11 deletions(-)

diff --git a/documentation/localization_guide/README.md b/documentation/localization_guide/README.md
index d4b50af..02ac61e 100644
--- a/documentation/localization_guide/README.md
+++ b/documentation/localization_guide/README.md
@@ -20,9 +20,9 @@ Current translation status:
 
 | Locale | Progress | Translated |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 153 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 153 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 153 of 153 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 157 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 157 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 157 of 157 |
 
 ---
 
diff --git a/documentation/localization_guide/README.ru.md b/documentation/localization_guide/README.ru.md
index 1c4c7e0..8a28b3d 100644
--- a/documentation/localization_guide/README.ru.md
+++ b/documentation/localization_guide/README.ru.md
@@ -20,9 +20,9 @@
 
 | Локаль | Прогресс | Переведено |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 153 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 153 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 153 из 153 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 157 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 157 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 157 из 157 |
 
 ---
 
diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo
index 71e89d71fd38dbc3f0b0bbe95dcf91bd71e501ba..f5b88244345405ea4ae491cf5d3e29fe735cd1d3 100644
GIT binary patch
delta 16
XcmX@ie3*H{WL6_1D^t^rGqf22Fp~u`

delta 16
XcmX@ie3*H{WL5)9D`UfrGqf22Fqs80

diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
index 4145e5e..16abdbe 100644
--- a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 09:31+0500\n"
+"POT-Creation-Date: 2025-06-08 22:55+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: de_DE\n"
@@ -20,6 +20,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 2.17.0\n"
 
+msgid "Remove from Favorites"
+msgstr ""
+
+msgid "Add to Favorites"
+msgstr ""
+
 msgid "Remove from Desktop"
 msgstr ""
 
@@ -47,6 +53,14 @@ msgstr ""
 msgid "Add to Steam"
 msgstr ""
 
+#, python-brace-format
+msgid "Added '{0}' to favorites"
+msgstr ""
+
+#, python-brace-format
+msgid "Removed '{0}' from favorites"
+msgstr ""
+
 msgid "Error"
 msgstr ""
 
diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo
index 1c9c621316c433ce09621e87918dc6b8898ca4e6..7407abf551b316c6c34a83eed54db35340dd0aef 100644
GIT binary patch
delta 16
XcmX@ie3*H{WL6_1D^t^rGqf22Fp~u`

delta 16
XcmX@ie3*H{WL5)9D`UfrGqf22Fqs80

diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
index 74cfe8c..482a778 100644
--- a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 09:31+0500\n"
+"POT-Creation-Date: 2025-06-08 22:55+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: es_ES\n"
@@ -20,6 +20,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 2.17.0\n"
 
+msgid "Remove from Favorites"
+msgstr ""
+
+msgid "Add to Favorites"
+msgstr ""
+
 msgid "Remove from Desktop"
 msgstr ""
 
@@ -47,6 +53,14 @@ msgstr ""
 msgid "Add to Steam"
 msgstr ""
 
+#, python-brace-format
+msgid "Added '{0}' to favorites"
+msgstr ""
+
+#, python-brace-format
+msgid "Removed '{0}' from favorites"
+msgstr ""
+
 msgid "Error"
 msgstr ""
 
diff --git a/portprotonqt/locales/messages.pot b/portprotonqt/locales/messages.pot
index 14160cf..85236e1 100644
--- a/portprotonqt/locales/messages.pot
+++ b/portprotonqt/locales/messages.pot
@@ -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-06-08 09:31+0500\n"
+"POT-Creation-Date: 2025-06-08 22:55+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"
@@ -18,6 +18,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 2.17.0\n"
 
+msgid "Remove from Favorites"
+msgstr ""
+
+msgid "Add to Favorites"
+msgstr ""
+
 msgid "Remove from Desktop"
 msgstr ""
 
@@ -45,6 +51,14 @@ msgstr ""
 msgid "Add to Steam"
 msgstr ""
 
+#, python-brace-format
+msgid "Added '{0}' to favorites"
+msgstr ""
+
+#, python-brace-format
+msgid "Removed '{0}' from favorites"
+msgstr ""
+
 msgid "Error"
 msgstr ""
 
diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo
index 6ce2539ba3f46b89cc5f313d42c068e26e5fbdbb..e5a07da7a080f2ca6188f5067f90240f95cc0bdf 100644
GIT binary patch
delta 2849
zcmZYAeN5F=9LMp4JmbxT3-aW{PZ6%7z!e8dLYtY&#2^eOxzaLFX%LGTV@ta}q%En<
z22?I}ZVsuoLM}3_RI(LYZRusse=3qKTQ)VDrE4nG`@=nzQ@H$I=lp)>;d{R4{4V>e
zFK_bw7#G%V_&LbmWd2;?s{Q|SC)${Fsv{VMX)(r3z_~aP7vXqZi7u?fB&<e__lkY~
z9xkWeiEHs1t}w=DR?s_=8|^p~51=Oe0yW?z48t23fw!z7JR~L)g%4mH>bZkn^dL=5
zCAzT+6>u}E{p&c3`OP~Nw4%@LgR?k^`VeZsKar2Q&1GytdXG)WMRf<2v`kDvFFII)
zQ?V8`aSP7C{WuN#CG(pL6w>f-oQ`QklYvW7_iIrd_Tn5oiXQv{Q!(tpVBJAwu-Mk?
zP~)}Z6zoH7$$3;puA`4JOa$R+paj(3%|lJN!umX_;~rEdI+5)&$4~+FV?18Q1T?X1
z2TVe3-E7RmY;>>+HU6$x@~=V%H(0#+3bkkFY{Ltv6=^i3IvsUZa!@H>fcaR2T4^I{
zA#bC$(2rXA*QoZtSg#^On?K@w!4-#-4@Ec?H9-X`^-m#-GTTsF@)joIaa2G7<YT_$
zqJF<wZ=&9oQB(#ZNe@NiLS@)NWxl{ifo(9wr~xZcsoh{3)Y<1-P>1mi)IhDMRCl5R
zJc(M-8Prw{qb3+ZW!6Pw1(<@W&p?u3d|nFbxWqnKjY`>br~$U4R<airz&_N94xs|>
zKn>i5YTt+DIDk)J68qp_Evo;UsEON<em>JlL5HdfJ$M2Y;YAF^>-PCg)E<VC|8W?N
z3e1HHBmouJH2Zv}eeOY>DX)EAXkCnvdjFSE&_Jd3!5SP0Vee6gGcDN|K4uA*EPNVu
z7FtlL_9Gv2mWvmE#R^Q}%Rq;*9-FZRKg2L@wqO^IXMVGYQFh>R+=<_yzDP>h2c3l)
zRB9Vhnc9iU&|cJ8IB4(taSio;BzBX?w@)_apfbD8x*LnAe~i9a6s}OvAxvPo6iqtn
zP(6w|8>>-o!zNT<EvP*_f(qypTz~^ufTPIAEOdgeZzYyd---|8w^rA5@~;jhyu50#
z3m?bxSb`aRUp|5jsCK=m0j{A|kWEzTSBtuT0@d!eHIFY3^{+>@_oD*)9XU>BV!AKb
z(UTtB+l@Gv2W?o0r*RIBB4e6NHeQ)2#TRisX5t|7rkOjaRL62rW(sfvE<*+Q0qSgA
z!X;Rw{O3^Ej*6@sSK=Tp#;LP|@A)&R37SxQ--4@fAL`IuK?O2}3$dDFoQE%<w(t<@
z4Ed4dnbSxTjPD|abrhmmrVi6)T!>qcAWbhmhC@h9W)A65y9(6An{9m`Dy1KxCi)Ux
zcn&#_X4pE4$<!zFDsh&4CZB>%YdPwxu?A^lb|4?q#YKm1$kzWt1(Lzh=0!1Or~zwH
z0qsU@RUbO|Ju0v}sLZ%Yw=!LTUcLWMQqaVEP<wtD)o>Vf$|D$c0w$xjU>0iPA|&f(
zEplGXtN1=1!451V{}jy)jKYlE;9<-`jnjZ*|Nmd3pwroj%EZqYfg`AfVe^9~V<`1(
zjKf^(Qgl(Tw9l*2O}z%S(ybVY2T+;lK=nU?6WkQeQc#1R=C^(tT^zma*{1r2n#Ssv
zT5m<CCRSBdS2>=Sy<0s_W4*KS-)BAUnEaT#nij@qq}0_n{nvJ5Lw(&{t=Gp7j0?}r
zeJC%lH8U|SSn64o*c}r4ZlEvFJ-97!G;osNQ-R*rt4RepZIrsX=MQ`q=;i+LK(7rV
v;CHCq+owFWqNh3ejN5(ha6N%jt=_DHoWp@*40qpPjCB8S^y%4@l^6aGI-hyg

delta 2575
zcmYM#e@s<n9LMpmT={V?q<OtTfW-wuAe8tKCK^q%DYL*7L8+Fm(lTqN=rS9f)J#)q
zva7dEEw@HYv)UL&NV3=>Sj@&+Yt?c@vp<|VY`I0lwt9cK&lbC$*Llu)&hvbq@Ao;!
ztIwX;;Ee>Q9yR`s@oyIYu1!_z|G)Eovpkx;n1q*b8eYR>yoo887>GR|LR~M#Wmtue
z;XYiBqnL<Abh^vTvvN)}!CKTnuc2Z%gb8@q`7Sb-y^puxDR;dKOKA5ZKO5sP6DROi
zoW`K)pM?cD7qx(f$?Klk22N7B(1e=sAo8<T4wDn&<b<x>h4I|)!wek2F#d*jVjP_{
zaW>{)8D?R<a|h<qK8o4QZ|69fjlZ}XafGJ_W@8a9#eCd`nRv{#yO0#xh->?%Ga&6E
zyaQ`cCEbk5$N{9Qoj{Fq2EE%k8RSG0jye^-9t<OkvMQu(wj32uJ%(^M-iA>O;>W1c
zeTfy=k71lZjh{Iq=0aqx_V^6yPnm2J7s9v$wW260^<SVi%h#xse~a_*d(=t;%&L{l
zL6xu?weoeS{>{$4$RqX^YQe`*0iF$#e@$?O3#!SVNU)YlzEqNYOv74KKpT;ty~IJ!
zz2R&@y&XqT8F&x**#{gnVHYa(!$^{C1U256$B9z=x9i{|uj&v)ZN?nrmKCA`t3s`$
z7B%2{R4TWi0%~^ceaKoYg6iMyu6Ls{G=O^E`<)X-egie(Kd2S>D6Aq(LJb^5J&=Jb
zu@LL<RSe@W>iIuV6OW^w3s4vBp&;gC7AoLn=+paO?>aQ1YPJ#M@C8(0TTp@QL<P3T
z-QVx-A4Kh;h`WE%*@g<>G-{j<cfAJ(rm#sdi}~$M3Y1QVcASfSsNFy2t|xPIHSIjq
zX4{23F^V7IbUp%yum=^$QabIyRk#;_Kz%0~8C0d-j+3d!6y~>gI8jQDqiTIxH?SAi
z;1FsP=1{m|T!2dDCTA;F(LRR-IF8ywVZKKCAeC^?-dTa#3+qvveH(g;EXs*$)`6P1
zAMeBOuoC^ey!>nl2fdz8;|e^058zMEv~a9{1M2=^d=y9VVJzjHeGr>b<Mw5fe+@9f
z1+Acx@1-7m8FhUS)iHr^<s#GoyHNdmk-6<AQclYxIz3m0D&<z3gD3HRynsdM&x>WC
zBF~GZrjZL<x$!*SjTey@!4mlhDAlu2nRy7G!j-6r&Z73l4P1iNbiM~$P=TGtNAMyp
z#zN}Di(#8l<L~x3QSGC+3fr&*$5DY46vqB{tiw{;ub^sp2DOQLktEp#WN~&KpTrOk
zYcDn7eB6)3V0~DFmys#Vn@2v>aUE*nCf9C5rL-G0(J(3lBgp=;t4=@pO{blY+DnU3
zoAn9g1+wkP&)(*slJubZU5>Rq^Kq38Zl$P!YLRl<a~Q@JR3M)qRk40lYOkOI4)987
zqJ^j$uSWH2MQ!TOaT*SxQvNe)zA-G+`=3m~*nd`mr*I8^h#FlJv~x(pA5oic6g5yT
z^-v)5QJZlsYUNQ(z)w;AK64JCkM^&qz%I!ePX6K~1^s2Q4ymY;WS}M}#zd?|WugIt
z_#&#`>!|xj$|4i~>PTH+W~UdpHxT(Il-~Jo=&QK+lF9|;rIEq3+}K&?^|X#Dk-_xb
S&diK(DDq5kZfAFKdHjEGQv?VA

diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
index d555b37..ca200aa 100644
--- a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
@@ -9,8 +9,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 09:31+0500\n"
-"PO-Revision-Date: 2025-06-08 09:31+0500\n"
+"POT-Creation-Date: 2025-06-08 22:55+0500\n"
+"PO-Revision-Date: 2025-06-08 22:55+0500\n"
 "Last-Translator: \n"
 "Language: ru_RU\n"
 "Language-Team: ru_RU <LL@li.org>\n"
@@ -21,6 +21,12 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 2.17.0\n"
 
+msgid "Remove from Favorites"
+msgstr "Удалить из Избранного"
+
+msgid "Add to Favorites"
+msgstr "Добавить в Избранное"
+
 msgid "Remove from Desktop"
 msgstr "Удалить с рабочего стола"
 
@@ -48,6 +54,14 @@ msgstr "Удалить из Steam"
 msgid "Add to Steam"
 msgstr "Добавить в Steam"
 
+#, python-brace-format
+msgid "Added '{0}' to favorites"
+msgstr "Добавление '{0}' в избранное"
+
+#, python-brace-format
+msgid "Removed '{0}' from favorites"
+msgstr "Удаление '{0}' из избранного"
+
 msgid "Error"
 msgstr "Ошибка"
 

From 61115411e7eff01b5c6835466a7d2457a03cd3b5 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 22:57:43 +0500
Subject: [PATCH 22/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 820b8bf..cd1231a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,8 +18,8 @@
 - Сохранение и восстановление размера окна при перезапуске
 - Переключатель полноэкранного режима приложения
 - Пункт в контекстном меню «Открыть папку игры»
-- Пункт в контекстном меню «Добавить в Steam»
-- Пункт в контекстном меню «Удалить из Steam»
+- Пункты в контекстном меню «Добавить в Steam» и «Удалить из Steam»
+- Пункты в контекстном меню «Добавить в Избранное» и «Удалить из Избранного» для переключения статуса избранного через геймпад
 - Метод сортировки «Сначала избранное»
 - Настройка автоматического перехода в полноэкранный режим при подключении геймпада (по умолчанию отключена)
 - Обработчики для QMenu и QComboBox при управлении геймпадом

From 68a52d69801c50a8eb2b0250efac750253bd31cd Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 23:06:14 +0500
Subject: [PATCH 23/47] feat: optimize game grid update for search performance

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/main_window.py | 59 ++++++++++++++++++++++++-------------
 1 file changed, 39 insertions(+), 20 deletions(-)

diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index e5d1246..66c1f70 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -102,6 +102,7 @@ class MainWindow(QMainWindow):
         self.setMinimumSize(800, 600)
 
         self.games = []
+        self.filtered_games = self.games
         self.game_processes = []
         self.target_exe = None
         self.current_running_button = None
@@ -543,10 +544,10 @@ class MainWindow(QMainWindow):
         """Filters games based on search text and updates the grid."""
         text = self.searchEdit.text().strip().lower()
         if text == "":
-            self.updateGameGrid()  # Use self.games directly
+            self.filtered_games = self.games
         else:
-            filtered = [game for game in self.games if text in game[0].lower()]
-            self.updateGameGrid(filtered)
+            self.filtered_games = [game for game in self.games if text in game[0].lower()]
+        self.updateGameGrid(self.filtered_games)
 
     def createInstalledTab(self):
         self.gamesLibraryWidget = QWidget()
@@ -620,22 +621,37 @@ class MainWindow(QMainWindow):
         if games_list is None:
             games_list = self.games
         if not games_list:
-            self.clearLayout(self.gamesListLayout)
+            # Скрываем все карточки, если список пуст
+            for card in self.game_card_cache.values():
+                card.hide()
             self.game_card_cache.clear()
             self.pending_images.clear()
+            self.gamesListWidget.updateGeometry()
             return
 
-        # Create a set of game names for quick lookup
+        # Создаем словарь текущих игр для быстрого поиска
         current_games = {game_data[0]: game_data for game_data in games_list}
 
-        # Check if the grid is already up-to-date
-        if set(current_games.keys()) == set(self.game_card_cache.keys()) and self.card_width == getattr(self, '_last_card_width', None):
-            return  # No changes needed, skip update
+        # Проверяем, изменился ли список игр или размер карточек
+        current_game_names = set(current_games.keys())
+        cached_game_names = set(self.game_card_cache.keys())
+        card_width_changed = self.card_width != getattr(self, '_last_card_width', None)
 
-        # Track if layout has changed to decide if geometry update is needed
+        if current_game_names == cached_game_names and not card_width_changed:
+            # Список игр и размер карточек не изменились, обновляем только видимость
+            search_text = self.searchEdit.text().strip().lower()
+            for game_name, card in self.game_card_cache.items():
+                card.setVisible(search_text in game_name.lower() or not search_text)
+            self.loadVisibleImages()
+            return
+
+        # Обновляем размер карточек, если он изменился
+        if card_width_changed:
+            for card in self.game_card_cache.values():
+                card.setFixedWidth(self.card_width + 20)  # Учитываем extra_margin в GameCard
+
+        # Удаляем карточки, которых больше нет в списке
         layout_changed = False
-
-        # Remove cards for games no longer in the list
         for card_key in list(self.game_card_cache.keys()):
             if card_key not in current_games:
                 card = self.game_card_cache.pop(card_key)
@@ -645,11 +661,14 @@ class MainWindow(QMainWindow):
                     del self.pending_images[card_key]
                 layout_changed = True
 
-        # Add or update cards for current games
+        # Добавляем новые карточки и обновляем существующие
         for game_data in games_list:
             game_name = game_data[0]
+            search_text = self.searchEdit.text().strip().lower()
+            should_be_visible = search_text in game_name.lower() or not search_text
+
             if game_name not in self.game_card_cache:
-                # Create new card
+                # Создаем новую карточку
                 card = GameCard(
                     *game_data,
                     select_callback=self.openGameDetailPage,
@@ -657,7 +676,7 @@ class MainWindow(QMainWindow):
                     card_width=self.card_width,
                     context_menu_manager=self.context_menu_manager
                 )
-                # Connect context menu signals
+                # Подключаем сигналы контекстного меню
                 card.editShortcutRequested.connect(self.context_menu_manager.edit_game_shortcut)
                 card.deleteGameRequested.connect(self.context_menu_manager.delete_game)
                 card.addToMenuRequested.connect(self.context_menu_manager.add_to_menu)
@@ -670,18 +689,18 @@ class MainWindow(QMainWindow):
                 self.game_card_cache[game_name] = card
                 self.gamesListLayout.addWidget(card)
                 layout_changed = True
-            elif self.card_width != getattr(self, '_last_card_width', None):
-                # Update size only if card_width has changed
+            else:
+                # Обновляем видимость существующей карточки
                 card = self.game_card_cache[game_name]
-                card.setFixedWidth(self.card_width + 20)  # Account for extra_margin in GameCard
+                card.setVisible(should_be_visible)
 
-        # Store the current card_width
+        # Сохраняем текущий card_width
         self._last_card_width = self.card_width
 
-        # Trigger lazy image loading for visible cards
+        # Загружаем изображения для видимых карточек
         self.loadVisibleImages()
 
-        # Update layout geometry only if the layout has changed
+        # Обновляем геометрию только при необходимости
         if layout_changed:
             self.gamesListWidget.updateGeometry()
 

From b0ec4487ca25c41cdd906b73ffa394258f2b0a32 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 23:25:48 +0500
Subject: [PATCH 24/47] feat: add styling to QCheckBox and overlay buttons

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/main_window.py            |  3 ++-
 portprotonqt/system_overlay.py         | 10 ++++-----
 portprotonqt/themes/standart/styles.py | 29 ++++++++++++++++++++++++++
 3 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 66c1f70..2153923 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -972,7 +972,7 @@ class MainWindow(QMainWindow):
 
         # 5. Fullscreen setting for application
         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.fullscreenTitle = QLabel(_("Application Fullscreen Mode:"))
         self.fullscreenTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
@@ -984,6 +984,7 @@ class MainWindow(QMainWindow):
         # 6. Automatic fullscreen on gamepad connection
         self.autoFullscreenGamepadCheckBox = QCheckBox(_("Auto Fullscreen on Gamepad connected"))
         self.autoFullscreenGamepadCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        self.autoFullscreenGamepadCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
         self.autoFullscreenGamepadTitle = QLabel(_("Auto Fullscreen on Gamepad connected:"))
         self.autoFullscreenGamepadTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
         self.autoFullscreenGamepadTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
diff --git a/portprotonqt/system_overlay.py b/portprotonqt/system_overlay.py
index f62bb10..80b64f9 100644
--- a/portprotonqt/system_overlay.py
+++ b/portprotonqt/system_overlay.py
@@ -22,35 +22,35 @@ class SystemOverlay(QDialog):
 
         # Reboot button
         reboot_button = QPushButton(_("Reboot"))
-        #reboot_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        reboot_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
         reboot_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
         reboot_button.clicked.connect(self.reboot)
         layout.addWidget(reboot_button)
 
         # Shutdown button
         shutdown_button = QPushButton(_("Shutdown"))
-        #shutdown_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        shutdown_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
         shutdown_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
         shutdown_button.clicked.connect(self.shutdown)
         layout.addWidget(shutdown_button)
 
         # Suspend button
         suspend_button = QPushButton(_("Suspend"))
-        #suspend_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        suspend_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
         suspend_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
         suspend_button.clicked.connect(self.suspend)
         layout.addWidget(suspend_button)
 
         # Exit application button
         exit_button = QPushButton(_("Exit Application"))
-        #exit_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        exit_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
         exit_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
         exit_button.clicked.connect(self.exit_application)
         layout.addWidget(exit_button)
 
         # Cancel button
         cancel_button = QPushButton(_("Cancel"))
-        #cancel_button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
+        cancel_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
         cancel_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
         cancel_button.clicked.connect(self.reject)
         layout.addWidget(cancel_button)
diff --git a/portprotonqt/themes/standart/styles.py b/portprotonqt/themes/standart/styles.py
index f353ff8..1467e6b 100644
--- a/portprotonqt/themes/standart/styles.py
+++ b/portprotonqt/themes/standart/styles.py
@@ -90,6 +90,13 @@ SEARCH_EDIT_STYLE = """
     }
 """
 
+SETTINGS_CHECKBOX_STYLE = """
+    QCheckBox:focus {
+            border: 2px solid #409EFF;
+            background: #404554;
+        }
+"""
+
 # ОТКЛЮЧАЕМ РАМКУ У QScrollArea
 SCROLL_AREA_STYLE = """
     QWidget {
@@ -206,6 +213,28 @@ ACTION_BUTTON_STYLE = """
     }
 """
 
+# СТИЛЬ КНОПОК ОВЕРЛЕЯ
+OVERLAY_BUTTON_STYLE = """
+    QPushButton {
+        background: #3f424d;
+        border: 1px solid rgba(255, 255, 255, 0.20);
+        border-radius: 10px;
+        color: #ffffff;
+        font-size: 16px;
+        font-family: 'Play';
+    }
+    QPushButton:hover {
+        background: #282a33;
+    }
+    QPushButton:pressed {
+        background: #282a33;
+    }
+    QPushButton:focus {
+        border: 2px solid #409EFF;
+        background-color: #404554;
+    }
+"""
+
 # ТЕКСТОВЫЕ СТИЛИ: ЗАГОЛОВКИ И ОСНОВНОЙ КОНТЕНТ
 TAB_TITLE_STYLE = "font-family: 'Play'; font-size: 24px; color: #ffffff; background-color: none;"
 CONTENT_STYLE = """

From 6fa145ee130044332ec146d2e53c8ab34eb46525 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Sun, 8 Jun 2025 23:36:01 +0500
Subject: [PATCH 25/47] feat: add styling to Context Menu

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/context_menu_manager.py   |  1 +
 portprotonqt/themes/standart/styles.py | 34 ++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py
index 2918d1e..ad853c5 100644
--- a/portprotonqt/context_menu_manager.py
+++ b/portprotonqt/context_menu_manager.py
@@ -41,6 +41,7 @@ class ContextMenuManager:
         """
 
         menu = QMenu(self.parent)
+        menu.setStyleSheet(self.theme.CONTEXT_MENU_STYLE)
 
         favorites = read_favorites()
         is_favorite = game_card.name in favorites
diff --git a/portprotonqt/themes/standart/styles.py b/portprotonqt/themes/standart/styles.py
index 1467e6b..d66468a 100644
--- a/portprotonqt/themes/standart/styles.py
+++ b/portprotonqt/themes/standart/styles.py
@@ -8,6 +8,40 @@ current_theme_name = read_theme_from_config()
 favoriteLabelSize = 48, 48
 pixmapsScaledSize = 60, 60
 
+CONTEXT_MENU_STYLE = """
+    QMenu {
+        background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
+            stop:0 rgba(40, 40, 40, 0.95),
+            stop:1 rgba(25, 25, 25, 0.95));
+        border: 1px solid rgba(255, 255, 255, 0.15);
+        border-radius: 12px;
+        color: #ffffff;
+        font-family: 'Play';
+        font-size: 16px;
+        padding: 5px;
+    }
+    QMenu::item {
+        padding: 8px 20px;
+        background: transparent;
+        border-radius: 8px;
+        color: #ffffff;
+    }
+    QMenu::item:selected {
+        background: #282a33;
+        color: #09bec8;
+    }
+    QMenu::item:hover {
+        background: #282a33;
+        color: #09bec8;
+    }
+    QMenu::item:focus {
+        background: #409EFF;
+        color: #ffffff;
+        border: 1px solid rgba(255, 255, 255, 0.3);
+        border-radius: 8px;
+    }
+"""
+
 # СТИЛЬ ШАПКИ ГЛАВНОГО ОКНА
 MAIN_WINDOW_HEADER_STYLE = """
     QFrame {

From 61680ed97f9b7ccb70d5580ccfe702577273c20c Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Mon, 9 Jun 2025 09:56:25 +0500
Subject: [PATCH 26/47] chore: update program name to PortProtonQt

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 README.md                                     |   2 +-
 dev-scripts/l10n.py                           |   4 ++--
 portprotonqt/cli.py                           |   2 +-
 portprotonqt/config_utils.py                  |  10 +++++-----
 portprotonqt/context_menu_manager.py          |   6 +++---
 portprotonqt/downloader.py                    |   2 +-
 portprotonqt/egs_api.py                       |   6 +++---
 portprotonqt/image_utils.py                   |   3 +--
 .../locales/de_DE/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/de_DE/LC_MESSAGES/messages.po     |   6 +++---
 .../locales/es_ES/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/es_ES/LC_MESSAGES/messages.po     |   6 +++---
 portprotonqt/locales/messages.pot             |   8 ++++----
 .../locales/ru_RU/LC_MESSAGES/messages.mo     | Bin 13291 -> 13316 bytes
 .../locales/ru_RU/LC_MESSAGES/messages.po     |  10 +++++-----
 portprotonqt/main_window.py                   |  16 ++++++++--------
 portprotonqt/steam_api.py                     |   2 +-
 portprotonqt/theme_manager.py                 |   2 +-
 .../themes/standart-light/metainfo.ini        |   2 +-
 portprotonqt/themes/standart/metainfo.ini     |   2 +-
 portprotonqt/time_utils.py                    |   2 +-
 21 files changed, 45 insertions(+), 46 deletions(-)

diff --git a/README.md b/README.md
index aa5f634..dacdf9e 100644
--- a/README.md
+++ b/README.md
@@ -60,7 +60,7 @@
 - [X] Исправить частичное применение тем на лету
 - [X] Исправить наложение подписей скриншотов при первом перелистывании в полноэкранном режиме
 - [ ] Добавить поддержку GOG (?)
-- [ ] Определиться с названием (PortProtonQt или PortProtonQT или вообще третий вариант)
+- [X] Определиться с названием (PortProtonQt или PortProtonQT или вообще третий вариант)
 - [ ] Добавить данные с HowLongToBeat на страницу с деталями игры (?)
 - [ ] Добавить виброотдачу на геймпаде при запуске игры (?)
 
diff --git a/dev-scripts/l10n.py b/dev-scripts/l10n.py
index 4acbb5b..6a9ff5c 100755
--- a/dev-scripts/l10n.py
+++ b/dev-scripts/l10n.py
@@ -106,7 +106,7 @@ def compile_locales() -> None:
 def extract_strings() -> None:
     input_dir = (Path(__file__).parent.parent / "portprotonqt").resolve()
     CommandLineInterface().run([
-        "pybabel", "extract", "--project=PortProtonQT",
+        "pybabel", "extract", "--project=PortProtonQt",
         f"--version={_get_version()}",
         "--strip-comment-tag",
         "--no-location",
@@ -231,7 +231,7 @@ def main(args) -> int:
     return 0
 
 if __name__ == "__main__":
-    parser = argparse.ArgumentParser(prog="l10n", description="Localization utility for PortProtonQT.")
+    parser = argparse.ArgumentParser(prog="l10n", description="Localization utility for PortProtonQt.")
     parser.add_argument("--create-new", nargs='+', type=str, default=False, help="Create .po for new locales")
     parser.add_argument("--update-all", action='store_true', help="Extract/update locales and update README coverage")
     parser.add_argument("--spellcheck", action='store_true', help="Run spellcheck on POT and PO files")
diff --git a/portprotonqt/cli.py b/portprotonqt/cli.py
index ed7d096..f781dfc 100644
--- a/portprotonqt/cli.py
+++ b/portprotonqt/cli.py
@@ -7,7 +7,7 @@ def parse_args():
     """
     Парсит аргументы командной строки.
     """
-    parser = argparse.ArgumentParser(description="PortProtonQT CLI")
+    parser = argparse.ArgumentParser(description="PortProtonQt CLI")
     parser.add_argument(
         "--fullscreen",
         action="store_true",
diff --git a/portprotonqt/config_utils.py b/portprotonqt/config_utils.py
index 3c9ca86..cf40558 100644
--- a/portprotonqt/config_utils.py
+++ b/portprotonqt/config_utils.py
@@ -10,7 +10,7 @@ _portproton_location = None
 # Пути к конфигурационным файлам
 CONFIG_FILE = os.path.join(
     os.getenv("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")),
-    "PortProtonQT.conf"
+    "PortProtonQt.conf"
 )
 
 PORTPROTON_CONFIG_FILE = os.path.join(
@@ -21,7 +21,7 @@ PORTPROTON_CONFIG_FILE = os.path.join(
 # Пути к папкам с темами
 xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
 THEMES_DIRS = [
-    os.path.join(xdg_data_home, "PortProtonQT", "themes"),
+    os.path.join(xdg_data_home, "PortProtonQt", "themes"),
     os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
 ]
 
@@ -472,14 +472,14 @@ def reset_config():
 
 def clear_cache():
     """
-    Очищает кэш PortProtonQT, удаляя папку кэша.
+    Очищает кэш PortProtonQt, удаляя папку кэша.
     """
     xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
-    cache_dir = os.path.join(xdg_cache_home, "PortProtonQT")
+    cache_dir = os.path.join(xdg_cache_home, "PortProtonQt")
     if os.path.exists(cache_dir):
         try:
             shutil.rmtree(cache_dir)
-            logger.info("Кэш PortProtonQT удалён: %s", cache_dir)
+            logger.info("Кэш PortProtonQt удалён: %s", cache_dir)
         except Exception as e:
             logger.error("Ошибка при удалении кэша: %s", e)
 
diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py
index ad853c5..4e5772b 100644
--- a/portprotonqt/context_menu_manager.py
+++ b/portprotonqt/context_menu_manager.py
@@ -12,7 +12,7 @@ from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_s
 from portprotonqt.dialogs import AddGameDialog
 
 class ContextMenuManager:
-    """Manages context menu actions for game management in PortProtonQT."""
+    """Manages context menu actions for game management in PortProtonQt."""
 
     def __init__(self, parent, portproton_location, theme, load_games_callback, update_game_grid_callback):
         """
@@ -258,7 +258,7 @@ class ContextMenuManager:
                 "XDG_DATA_HOME",
                 os.path.join(os.path.expanduser("~"), ".local", "share")
             )
-            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)
             if os.path.exists(custom_folder):
                 try:
                     shutil.rmtree(custom_folder)
@@ -417,7 +417,7 @@ class ContextMenuManager:
                     "XDG_DATA_HOME",
                     os.path.join(os.path.expanduser("~"), ".local", "share")
                 )
-                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)
 
                 ext = os.path.splitext(new_cover_path)[1].lower()
diff --git a/portprotonqt/downloader.py b/portprotonqt/downloader.py
index 8118b39..0e8e610 100644
--- a/portprotonqt/downloader.py
+++ b/portprotonqt/downloader.py
@@ -303,7 +303,7 @@ class Downloader(QObject):
 
         local_path = os.path.join(
             os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")),
-            "PortProtonQT", "legendary_cache", "legendary"
+            "PortProtonQt", "legendary_cache", "legendary"
         )
 
         logger.info(f"Downloading legendary binary version {version} from {binary_url} to {local_path}")
diff --git a/portprotonqt/egs_api.py b/portprotonqt/egs_api.py
index a80a295..8f12c3f 100644
--- a/portprotonqt/egs_api.py
+++ b/portprotonqt/egs_api.py
@@ -22,7 +22,7 @@ def get_cache_dir() -> Path:
         "XDG_CACHE_HOME",
         os.path.join(os.path.expanduser("~"), ".cache")
     )
-    cache_dir = Path(xdg_cache_home) / "PortProtonQT"
+    cache_dir = Path(xdg_cache_home) / "PortProtonQt"
     cache_dir.mkdir(parents=True, exist_ok=True)
     return cache_dir
 
@@ -36,7 +36,7 @@ def get_egs_game_description_async(
     Asynchronously fetches the game description from the Epic Games Store API.
     Prioritizes GraphQL API with namespace for slug and description.
     Falls back to legacy API if GraphQL provides a slug but no description.
-    Caches results in ~/.cache/PortProtonQT/egs_app_{app_name}.json.
+    Caches results in ~/.cache/PortProtonQt/egs_app_{app_name}.json.
     Handles DNS resolution failures gracefully.
     """
     cache_dir = get_cache_dir()
@@ -423,7 +423,7 @@ def _continue_loading_egs_games(legendary_path: str, callback: Callable[[list[tu
             except Exception as e:
                 logger.warning("Error processing metadata for %s: %s", app_name, str(e))
 
-            image_folder = os.path.join(os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")), "PortProtonQT", "images")
+            image_folder = os.path.join(os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")), "PortProtonQt", "images")
             local_path = os.path.join(image_folder, f"{app_name}.jpg") if cover_url else ""
 
             def on_description_fetched(api_description: str):
diff --git a/portprotonqt/image_utils.py b/portprotonqt/image_utils.py
index 0c2dda5..922f23f 100644
--- a/portprotonqt/image_utils.py
+++ b/portprotonqt/image_utils.py
@@ -35,10 +35,9 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
             y = (scaled.height() - height) // 2
             cropped = scaled.copy(x, y, width, height)
             callback(cropped)
-            # Removed: pixmap = None (unnecessary, causes type error)
 
         xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
-        image_folder = os.path.join(xdg_cache_home, "PortProtonQT", "images")
+        image_folder = os.path.join(xdg_cache_home, "PortProtonQt", "images")
         os.makedirs(image_folder, exist_ok=True)
 
         if cover and cover.startswith("https://steamcdn-a.akamaihd.net/steam/apps/"):
diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo
index f5b88244345405ea4ae491cf5d3e29fe735cd1d3..c07c51151cedee2358bfb4d0b5823d9a26dd8127 100644
GIT binary patch
delta 18
ZcmX@ie3*H{M0QIB14}DY<Bc=4838%U1&#m!

delta 18
ZcmX@ie3*H{M0N`WBO@zQ(~UE<838$?1&ROw

diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
index 16abdbe..b4d17ec 100644
--- a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
@@ -1,6 +1,6 @@
-# German (Germany) translations for PortProtonQT.
+# German (Germany) translations for PortProtonQt.
 # Copyright (C) 2025 boria138
-# This file is distributed under the same license as the PortProtonQT
+# This file is distributed under the same license as the PortProtonQt
 # project.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 #
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 22:55+0500\n"
+"POT-Creation-Date: 2025-06-09 09:53+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: de_DE\n"
diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo
index 7407abf551b316c6c34a83eed54db35340dd0aef..432121931b073d02a47c7f7d1dd1139e8c13e683 100644
GIT binary patch
delta 18
ZcmX@ie3*H{M0QIB14}DY<Bc=4838%U1&#m!

delta 18
ZcmX@ie3*H{M0N`WBO@zQ(~UE<838$?1&ROw

diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
index 482a778..6f0217d 100644
--- a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
@@ -1,6 +1,6 @@
-# Spanish (Spain) translations for PortProtonQT.
+# Spanish (Spain) translations for PortProtonQt.
 # Copyright (C) 2025 boria138
-# This file is distributed under the same license as the PortProtonQT
+# This file is distributed under the same license as the PortProtonQt
 # project.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 #
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 22:55+0500\n"
+"POT-Creation-Date: 2025-06-09 09:53+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: es_ES\n"
diff --git a/portprotonqt/locales/messages.pot b/portprotonqt/locales/messages.pot
index 85236e1..9e57c3a 100644
--- a/portprotonqt/locales/messages.pot
+++ b/portprotonqt/locales/messages.pot
@@ -1,15 +1,15 @@
-# Translations template for PortProtonQT.
+# Translations template for PortProtonQt.
 # Copyright (C) 2025 boria138
-# This file is distributed under the same license as the PortProtonQT
+# This file is distributed under the same license as the PortProtonQt
 # project.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 #
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: PortProtonQT 0.1.1\n"
+"Project-Id-Version: PortProtonQt 0.1.1\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 22:55+0500\n"
+"POT-Creation-Date: 2025-06-09 09:53+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"
diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo
index e5a07da7a080f2ca6188f5067f90240f95cc0bdf..e77f989564c594bcedbd8f64b5181f4e516b4f15 100644
GIT binary patch
delta 1393
zcmXZbT}YEr7{KvILn~9u&uMBd%gT@0Qp_BtF1J+NoJ}(d%NB~-hoTd+o0gU&NOYAK
zeHpZ%f*=%%AT+utX&?~@A%quEh(%W+2o?PweBtH%&N(~pInVo?XFocwbv}=d2rPz)
zw3tLPMWifMghyIvEI5F>Fo@|GLLOP7v10gEktQs{G#tf24B}JFU~&v+aT7MN$uOSA
ztN1lt0>WD9$PkIbVT{Ea7>#!^2A^O8zBJ}Tc#QF9BzCcG6DhzN+>I9vC-5NS_qYcm
z_{z;#fJ{kwW<X>Mos&$&VK?r;E2x15F&<}71Nwv|xQL}_GOuOs2<~Luiyj=ua{O+X
zy?t$c8|v@(u>pSt=+x1v$Pzh(W2hU?p>D8&X!HxJQC}QEou5Ztm&mDVJ?i_TsOv*W
zjIuFXBpGv1->XEu+5qOO&*+rVS-?CrapCMORj8Tiz+N20TwKOPOd`*k>RcMlj2F*f
zE9yaSQEMZNyjNi}?#0`vfqg(1=gTr3I};^2Y>Jmq54eeX_d)dHBw8_oC^V4$G{h`E
z+=tguukaOW4TX>-$^w!M`HQD8i?7sL8o~pdFSqCrtjwVsSCE**L4I`M1=NFwjPWFD
zO5dR#^aZ2wCsL2HYG@+Qsf>$JYsrIJtY<L=hcTb?<qjPlnWfQ%E5<mElGZ>fQ2&0N
zs2h%;1~h?sRUgrezfl8ABF~zceAHBXQP=mO9{dpX%3q__fvnQeD$it9EEb|(fgSbW
zW+eOKN9tAX;bWY^N7zaJ=}K%N1&bA^#aM&7&lqYT4^WHq4QeL-6_Wo*I>|+A3o{H0
zaUJv37>{*^r%+SfYy5s06BrMp9x#sU@dauorcvLY#{^tL{rwM0L0h~3+*EkfbWCfh
z*;;BVF3PhOTdh$I9*ehqpv&LY(`|A1`rB>h<4(KR;`Ue^c5{u#=B~{<;i-2<xqSY9
tOH-e(+kf8I-_vI^A3o}Gnd|Ltr@74Ow%58U?GA_6+1OY<6`Ara?LYhXs-6G<

delta 1368
zcmY+@Ur1AN6u|LQnUzgbSLyt_GN-AV>&8qigVHR}oQaV|S&NpA#L+gShh(H?&|Z4z
z#fKsYwSuxH5`h##_z;9h^b~?nLXl7}L6Aw`!|qFPxu5g9``zE~-19pd>i*vK+_FA8
zyGG<}mPnz96xl?$q@J4-&!Gcjn1^xXk~wZ}{D$qAlOvLgLA-`Be1b_ljNt@s#{DcZ
zfJbo<-{nbEm@6&$A}JWaG>o7H2Qd}zpcU_%&*ONC@f##}$tn<WVI`Jer|B@(F@A=n
zxQttH8(&K(#f4Fkbb<yRWZ+3G!Y<UrVwj0zs0qEoeK?CXxQbj-Z5P>v9q7e=Jc#d2
zEjtqPn^1qhiLLlCO3*~$*eP-tL#PW+p)T+P^$T2Nr7!lNKA%RN_s7)3?)rWZb$%Q*
zv7fjd(~A@Dm7pH&WvsyHU4jDyGgywR$eL2d!E0q&@G73iGMvY)xPn^h3~pMP8oY>&
zs0lts?TxQkk9E|)5^ta;_5z#PU*-u8@t|Ng=YkhdH|Rq>`xv(22x`+Uqb8Eija=mz
z?!jKvBYc3`Lvf^dGJ_OBzF;SA;w!b6y0M!5<vIaL$`l^Q1tcdar#?Ed9d+Ywb3B4t
z(x<2!y+sRtK>AS@O;^#zIFEX3FIAy7>j})p09LTS3=nY11UH?yV2*#ICgPyA`NxPC
zb-^CggoaU%>LuFo6KY~BsFkr&Z!L8V>ipBF8{bAf@`o7JiHii<<;l!SLmTQ5l%j52
zhg4n8ApI&s_z1^v6us1+P=4V?bQp<dtVCTWgqp}L)aD#Vt;CW+{gVm)niG@uCSI`N
zI-a{Q6AjZNsHN^OfA^!6aR7CLe%yffP%ANt`u;RpaSrwO&nT`-{_vH_wJEPt8>{Vx
zQS0%PyFG4qN~_n|=I;xH1HoQrgD>K*wHt23<8)U!-T%#N_Jt$P_K>eP+~bP`L$&tF
I!tABof5bGQ&;S4c

diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
index ca200aa..6e85830 100644
--- a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
@@ -1,6 +1,6 @@
-# Russian (Russia) translations for PortProtonQT.
+# Russian (Russia) translations for PortProtonQt.
 # Copyright (C) 2025 boria138
-# This file is distributed under the same license as the PortProtonQT
+# This file is distributed under the same license as the PortProtonQt
 # project.
 # FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 #
@@ -9,9 +9,9 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-08 22:55+0500\n"
-"PO-Revision-Date: 2025-06-08 22:55+0500\n"
-"Last-Translator: \n"
+"POT-Creation-Date: 2025-06-09 09:53+0500\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: ru_RU\n"
 "Language-Team: ru_RU <LL@li.org>\n"
 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 2153923..9660140 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -44,7 +44,7 @@ from datetime import datetime
 logger = get_logger(__name__)
 
 class MainWindow(QMainWindow):
-    """Main window of PortProtonQT."""
+    """Main window of PortProtonQt."""
     settings_saved = Signal()
     games_loaded = Signal(list)
     update_progress = Signal(int)  # Signal to update progress bar
@@ -73,10 +73,10 @@ class MainWindow(QMainWindow):
         self.settingsDebounceTimer.timeout.connect(self.applySettingsDelayed)
 
         read_time_config()
-        # Set LEGENDARY_CONFIG_PATH to ~/.cache/PortProtonQT/legendary
+        # Set LEGENDARY_CONFIG_PATH to ~/.cache/PortProtonQt/legendary_cache
         self.legendary_config_path = os.path.join(
             os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache")),
-            "PortProtonQT", "legendary_cache"
+            "PortProtonQt", "legendary_cache"
         )
         os.makedirs(self.legendary_config_path, exist_ok=True)
         os.environ["LEGENDARY_CONFIG_PATH"] = self.legendary_config_path
@@ -98,7 +98,7 @@ class MainWindow(QMainWindow):
         if not self.theme:
             self.theme = default_styles
         self.card_width = read_card_size()
-        self.setWindowTitle("PortProtonQT")
+        self.setWindowTitle("PortProtonQt")
         self.setMinimumSize(800, 600)
 
         self.games = []
@@ -384,7 +384,7 @@ class MainWindow(QMainWindow):
         builtin_custom_folder = os.path.join(repo_root, "portprotonqt", "custom_data")
         xdg_data_home = os.getenv("XDG_DATA_HOME",
                                 os.path.join(os.path.expanduser("~"), ".local", "share"))
-        user_custom_folder = os.path.join(xdg_data_home, "PortProtonQT", "custom_data")
+        user_custom_folder = os.path.join(xdg_data_home, "PortProtonQt", "custom_data")
         os.makedirs(user_custom_folder, exist_ok=True)
 
         builtin_cover = ""
@@ -780,7 +780,7 @@ class MainWindow(QMainWindow):
                         os.path.join(os.path.expanduser("~"), ".local", "share"))
                     custom_folder = os.path.join(
                         xdg_data_home,
-                        "PortProtonQT",
+                        "PortProtonQt",
                         "custom_data",
                         exe_name
                     )
@@ -1228,7 +1228,7 @@ class MainWindow(QMainWindow):
                     self.statusBar().showMessage(_("Theme '{0}' applied successfully").format(selected_theme), 3000)
                     xdg_data_home = os.getenv("XDG_DATA_HOME",
                                             os.path.join(os.path.expanduser("~"), ".local", "share"))
-                    state_file = os.path.join(xdg_data_home, "PortProtonQT", "state.txt")
+                    state_file = os.path.join(xdg_data_home, "PortProtonQt", "state.txt")
                     os.makedirs(os.path.dirname(state_file), exist_ok=True)
                     with open(state_file, "w", encoding="utf-8") as f:
                         f.write("theme_tab\n")
@@ -1251,7 +1251,7 @@ class MainWindow(QMainWindow):
     def restore_state(self):
         """Восстанавливает состояние приложения после перезапуска."""
         xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
-        state_file = os.path.join(xdg_cache_home, "PortProtonQT", "state.txt")
+        state_file = os.path.join(xdg_cache_home, "PortProtonQt", "state.txt")
         if os.path.exists(state_file):
             with open(state_file, encoding="utf-8") as f:
                 state = f.read().strip()
diff --git a/portprotonqt/steam_api.py b/portprotonqt/steam_api.py
index 514ba73..fed091e 100644
--- a/portprotonqt/steam_api.py
+++ b/portprotonqt/steam_api.py
@@ -49,7 +49,7 @@ def decode_text(text: str) -> str:
 def get_cache_dir():
     """Возвращает путь к каталогу кэша, создаёт его при необходимости."""
     xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
-    cache_dir = os.path.join(xdg_cache_home, "PortProtonQT")
+    cache_dir = os.path.join(xdg_cache_home, "PortProtonQt")
     os.makedirs(cache_dir, exist_ok=True)
     return cache_dir
 
diff --git a/portprotonqt/theme_manager.py b/portprotonqt/theme_manager.py
index 0686e7d..428b069 100644
--- a/portprotonqt/theme_manager.py
+++ b/portprotonqt/theme_manager.py
@@ -11,7 +11,7 @@ logger = get_logger(__name__)
 # Папка, где располагаются все дополнительные темы
 xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
 THEMES_DIRS = [
-    os.path.join(xdg_data_home, "PortProtonQT", "themes"),
+    os.path.join(xdg_data_home, "PortProtonQt", "themes"),
     os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
 ]
 
diff --git a/portprotonqt/themes/standart-light/metainfo.ini b/portprotonqt/themes/standart-light/metainfo.ini
index 1b5282d..e899572 100644
--- a/portprotonqt/themes/standart-light/metainfo.ini
+++ b/portprotonqt/themes/standart-light/metainfo.ini
@@ -1,5 +1,5 @@
 [Metainfo]
 author = BlackSnaker
 author_link =
-description = Стандартная тема PortProtonQT (светлый вариант)
+description = Стандартная тема PortProtonQt (светлый вариант)
 name = Light
diff --git a/portprotonqt/themes/standart/metainfo.ini b/portprotonqt/themes/standart/metainfo.ini
index 3814979..78b9340 100644
--- a/portprotonqt/themes/standart/metainfo.ini
+++ b/portprotonqt/themes/standart/metainfo.ini
@@ -1,5 +1,5 @@
 [Metainfo]
 author = Dervart
 author_link =
-description = Стандартная тема PortProtonQT (тёмный вариант)
+description = Стандартная тема PortProtonQt (тёмный вариант)
 name = Clean Dark
diff --git a/portprotonqt/time_utils.py b/portprotonqt/time_utils.py
index 71fc463..5829d89 100644
--- a/portprotonqt/time_utils.py
+++ b/portprotonqt/time_utils.py
@@ -10,7 +10,7 @@ logger = get_logger(__name__)
 def get_cache_file_path():
     """Возвращает путь к файлу кеша portproton_last_launch."""
     cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
-    return os.path.join(cache_home, "PortProtonQT", "last_launch")
+    return os.path.join(cache_home, "PortProtonQt", "last_launch")
 
 def save_last_launch(exe_name, launch_time):
     """

From 4a8033a0b7ce34383a71721cf593543418fce18a Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Mon, 9 Jun 2025 09:57:50 +0500
Subject: [PATCH 27/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index cd1231a..85ec136 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,7 @@
 - D-pad больше не переключает вкладки, только RB и LB
 - Кнопка добавления игры больше не фокусируется
 - Диалог добавления игры теперь открывается только в библиотеке
+- Удалены все упоминания PortProtonQT из кода и заменены на PortProtonQt
 
 ### Fixed
 - Обработка несуществующей темы с возвратом к «standard»

From fce2ef2d0d8c0d197054048c17670e91576b18d1 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Mon, 9 Jun 2025 11:16:44 +0500
Subject: [PATCH 28/47] fix(metainfo): update screenshots url

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 .../metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml    | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml b/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml
index 42af19e..a1eedb9 100644
--- a/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml
+++ b/build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml
@@ -7,7 +7,11 @@
   <summary>Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store</summary>
   <summary xml:lang="ru">Современный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store</summary>
   <description>
-    <p>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.</p>
+    <p>
+      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.</p>
+    <p xml:lang="ru">
+      Это приложение предоставляет стильный и интуитивно понятный графический интерфейс для управления и запуска игр из PortProton, Steam и Epic Games Store. Оно объединяет ваши игровые библиотеки в одном удобном хабе для простой навигации и организации. Лёгкая структура и кроссплатформенная поддержка обеспечивают целостный игровой опыт, устраняя необходимость в использовании нескольких лаунчеров. Уникальная интеграция с PortProton улучшает игровой процесс на Linux, позволяя с лёгкостью запускать Windows-игры с минимальными настройками.
+      </p>
   </description>
   <launchable type="desktop-id">ru.linux_gaming.PortProtonQt.desktop</launchable>
   <developer id="ru.linux_gaming">
@@ -41,7 +45,7 @@
       <caption xml:lang="ru">Детали игры</caption>
     </screenshot>
     <screenshot>
-      <image>https://git.linux-gaming.ru/Boria138/PortProtonQt/src/commit/9c4ad0b7bacac08849aff9036561de7b88a9bad2/portprotonqt/themes/standart/images/screenshots/%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8.png</image>
+      <image>https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/portprotonqt/themes/standart/images/screenshots/%D0%9D%D0%B0%D1%81%D1%82%D1%80%D0%BE%D0%B9%D0%BA%D0%B8.png</image>
       <caption>Settings</caption>
       <caption xml:lang="ru">Настройки</caption>
     </screenshot>

From 61f655c08f13ff2ca6d923bba9914d4477e2a3ed Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Mon, 9 Jun 2025 15:04:34 +0500
Subject: [PATCH 29/47] chore(readme): added all known issues

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 README.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/README.md b/README.md
index dacdf9e..b4685d6 100644
--- a/README.md
+++ b/README.md
@@ -63,6 +63,9 @@
 - [X] Определиться с названием (PortProtonQt или PortProtonQT или вообще третий вариант)
 - [ ] Добавить данные с HowLongToBeat на страницу с деталями игры (?)
 - [ ] Добавить виброотдачу на геймпаде при запуске игры (?)
+- [ ] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)
+- [ ] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63))
+- [ ] Скопировать логику управления с D-pad на стрелки с клавиатуры
 
 ### Установка (devel)
 

From 9373aa1329c6333bbc7654ae51514ea75ce36829 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Mon, 9 Jun 2025 19:53:41 +0500
Subject: [PATCH 30/47] feat: added "Return to Desktop" button to overlay

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/system_overlay.py | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/portprotonqt/system_overlay.py b/portprotonqt/system_overlay.py
index 80b64f9..6359dce 100644
--- a/portprotonqt/system_overlay.py
+++ b/portprotonqt/system_overlay.py
@@ -3,6 +3,7 @@ from PySide6.QtWidgets import QDialog, QVBoxLayout, QPushButton, QMessageBox
 from PySide6.QtWidgets import QApplication
 from PySide6.QtCore import Qt
 from portprotonqt.logger import get_logger
+import os
 from portprotonqt.localization import _
 
 logger = get_logger(__name__)
@@ -48,6 +49,18 @@ class SystemOverlay(QDialog):
         exit_button.clicked.connect(self.exit_application)
         layout.addWidget(exit_button)
 
+        # Return to Desktop button
+        desktop_button = QPushButton(_("Return to Desktop"))
+        desktop_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
+        desktop_button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        desktop_button.clicked.connect(self.return_to_desktop)
+        script_path = "/usr/bin/portprotonqt-session-select"
+        script_exists = os.path.isfile(script_path)
+        desktop_button.setEnabled(script_exists)
+        if not script_exists:
+            desktop_button.setToolTip(_("portprotonqt-session-select file not found at /usr/bin/"))
+        layout.addWidget(desktop_button)
+
         # Cancel button
         cancel_button = QPushButton(_("Cancel"))
         cancel_button.setStyleSheet(self.theme.OVERLAY_BUTTON_STYLE)
@@ -82,6 +95,15 @@ class SystemOverlay(QDialog):
             QMessageBox.warning(self, _("Error"), _("Failed to suspend the system"))
         self.accept()
 
+    def return_to_desktop(self):
+        try:
+            script_path = os.path.join(os.path.dirname(__file__), "portprotonqt-session-select")
+            subprocess.run([script_path, "desktop"], check=True)
+        except subprocess.CalledProcessError as e:
+            logger.error(f"Failed to return to desktop: {e}")
+            QMessageBox.warning(self, _("Error"), _("Failed to return to desktop"))
+        self.accept()
+
     def exit_application(self):
         QApplication.quit()
         self.accept()

From 149e80fa3382c0fc2e2bbe0f4cb018445bc60c6d Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Mon, 9 Jun 2025 21:31:52 +0500
Subject: [PATCH 31/47] feat: added Gamescope session

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 1 +
 README.md    | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 85ec136..b56ea83 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,6 +25,7 @@
 - Обработчики для QMenu и QComboBox при управлении геймпадом
 - Аргумент `--fullscreen` для запуска приложения в полноэкранном режиме
 - Оверлей на кнопку Xbox/PS для закрытия приложения, выключения, перезагрузки и перехода в спящий режим
+- [Gamescope сессия](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
 
 ### Changed
 - Обновлены все иконки
diff --git a/README.md b/README.md
index b4685d6..1355fb2 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@
 - [X] Вынести все константы, такие как уровень закругления карточек, в темы (частично выполнено)
 - [X] Добавить метаданные для тем (скриншоты, описание, домашняя страница и автор)
 - [ ] Продумать систему вкладок вместо текущей
-- [ ] Добавить сессию Gamescope, аналогичную той, что используется в SteamOS
+- [X] [Добавить сессию Gamescope, аналогичную той, что используется в SteamOS](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
 - [ ] Разработать адаптивный дизайн (за эталон берётся Steam Deck с разрешением 1280×800)
 - [ ] Переделать скриншоты для соответствия [гайдлайнам Flathub](https://docs.flathub.org/docs/for-app-authors/metainfo-guidelines/quality-guidelines#screenshots)
 - [X] Получать описания и названия игр из базы данных Steam

From b025e0bbcf692f0d42026fa0e9b204a37187ee75 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Tue, 10 Jun 2025 00:22:54 +0500
Subject: [PATCH 32/47] feat: rework QMessageBox handle and add focus style to
 it

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py          | 20 +++++++++++++++++---
 portprotonqt/themes/standart/styles.py |  4 ++++
 2 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 1b4fa7a..51da736 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -3,7 +3,7 @@ import threading
 from typing import Protocol, cast
 from evdev import InputDevice, ecodes, list_devices
 import pyudev
-from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView
+from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox
 from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer
 from PySide6.QtGui import QKeyEvent
 from portprotonqt.logger import get_logger
@@ -284,8 +284,22 @@ class InputManager(QObject):
                 self.dpad_timer.stop()  # Stop timer when D-pad is released
                 return
 
-            # Handle SystemOverlay or AddGameDialog navigation with D-pad
-            if isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0:
+            # Handle SystemOverlay, AddGameDialog, or QMessageBox navigation with D-pad
+            if isinstance(active, QDialog) and code == ecodes.ABS_HAT0X and value != 0:
+                if isinstance(active, QMessageBox):  # Specific handling for QMessageBox
+                    if not focused or not active.focusWidget():
+                        # If no widget is focused, focus the first focusable widget
+                        focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
+                        focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
+                        if focusables:
+                            focusables[0].setFocus(Qt.FocusReason.OtherFocusReason)
+                        return
+                    if value > 0:  # Right
+                        active.focusNextChild()
+                    elif value < 0:  # Left
+                        active.focusPreviousChild()
+                    return
+            elif isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0:  # Keep up/down for other dialogs
                 if not focused or not active.focusWidget():
                     # If no widget is focused, focus the first focusable widget
                     focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
diff --git a/portprotonqt/themes/standart/styles.py b/portprotonqt/themes/standart/styles.py
index d66468a..2ba0c9e 100644
--- a/portprotonqt/themes/standart/styles.py
+++ b/portprotonqt/themes/standart/styles.py
@@ -519,6 +519,10 @@ MESSAGE_BOX_STYLE = """
         background: #09bec8;
         border-color: rgba(255, 255, 255, 0.3);
     }
+    QMessageBox QPushButton:focus {
+        border: 2px solid #409EFF;
+        background: #404554;
+    }
 """
 
 # СТИЛИ ДЛЯ ВКЛАДКИ НАСТРОЕК PORTPROTON

From cc8c22e9725bf7c864c95a429a8440d87900efde Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Tue, 10 Jun 2025 10:25:42 +0500
Subject: [PATCH 33/47] chore(localization): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 documentation/localization_guide/README.md    |   6 +++---
 documentation/localization_guide/README.ru.md |   6 +++---
 .../locales/de_DE/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/de_DE/LC_MESSAGES/messages.po     |  11 ++++++++++-
 .../locales/es_ES/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/es_ES/LC_MESSAGES/messages.po     |  11 ++++++++++-
 portprotonqt/locales/messages.pot             |  11 ++++++++++-
 .../locales/ru_RU/LC_MESSAGES/messages.mo     | Bin 13316 -> 13603 bytes
 .../locales/ru_RU/LC_MESSAGES/messages.po     |  15 ++++++++++++---
 9 files changed, 48 insertions(+), 12 deletions(-)

diff --git a/documentation/localization_guide/README.md b/documentation/localization_guide/README.md
index 02ac61e..f4eea9e 100644
--- a/documentation/localization_guide/README.md
+++ b/documentation/localization_guide/README.md
@@ -20,9 +20,9 @@ Current translation status:
 
 | Locale | Progress | Translated |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 157 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 157 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 157 of 157 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 160 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 160 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 160 of 160 |
 
 ---
 
diff --git a/documentation/localization_guide/README.ru.md b/documentation/localization_guide/README.ru.md
index 8a28b3d..eabcaa1 100644
--- a/documentation/localization_guide/README.ru.md
+++ b/documentation/localization_guide/README.ru.md
@@ -20,9 +20,9 @@
 
 | Локаль | Прогресс | Переведено |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 157 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 157 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 157 из 157 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 160 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 160 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 160 из 160 |
 
 ---
 
diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo
index c07c51151cedee2358bfb4d0b5823d9a26dd8127..99bd71e23efe2b7b8f78ec83ad24c916681b9a98 100644
GIT binary patch
delta 19
acmX@ie3*H{1P((31w#WXBh!sDv>5?DAO)=e

delta 19
acmX@ie3*H{1P%jB1p`YfQ{#;@v>5?Dz6HJj

diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
index b4d17ec..c9e17da 100644
--- a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-09 09:53+0500\n"
+"POT-Creation-Date: 2025-06-10 10:25+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: de_DE\n"
@@ -503,6 +503,12 @@ msgstr ""
 msgid "Exit Application"
 msgstr ""
 
+msgid "Return to Desktop"
+msgstr ""
+
+msgid "portprotonqt-session-select file not found at /usr/bin/"
+msgstr ""
+
 msgid "Cancel"
 msgstr ""
 
@@ -515,6 +521,9 @@ msgstr ""
 msgid "Failed to suspend the system"
 msgstr ""
 
+msgid "Failed to return to desktop"
+msgstr ""
+
 msgid "just now"
 msgstr ""
 
diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo
index 432121931b073d02a47c7f7d1dd1139e8c13e683..23cb19531997d3eb614aa540c54d1360dc6df01f 100644
GIT binary patch
delta 19
acmX@ie3*H{1P((31w#WXBh!sDv>5?DAO)=e

delta 19
acmX@ie3*H{1P%jB1p`YfQ{#;@v>5?Dz6HJj

diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
index 6f0217d..f327b4e 100644
--- a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-09 09:53+0500\n"
+"POT-Creation-Date: 2025-06-10 10:25+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: es_ES\n"
@@ -503,6 +503,12 @@ msgstr ""
 msgid "Exit Application"
 msgstr ""
 
+msgid "Return to Desktop"
+msgstr ""
+
+msgid "portprotonqt-session-select file not found at /usr/bin/"
+msgstr ""
+
 msgid "Cancel"
 msgstr ""
 
@@ -515,6 +521,9 @@ msgstr ""
 msgid "Failed to suspend the system"
 msgstr ""
 
+msgid "Failed to return to desktop"
+msgstr ""
+
 msgid "just now"
 msgstr ""
 
diff --git a/portprotonqt/locales/messages.pot b/portprotonqt/locales/messages.pot
index 9e57c3a..836a96d 100644
--- a/portprotonqt/locales/messages.pot
+++ b/portprotonqt/locales/messages.pot
@@ -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-06-09 09:53+0500\n"
+"POT-Creation-Date: 2025-06-10 10:25+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"
@@ -501,6 +501,12 @@ msgstr ""
 msgid "Exit Application"
 msgstr ""
 
+msgid "Return to Desktop"
+msgstr ""
+
+msgid "portprotonqt-session-select file not found at /usr/bin/"
+msgstr ""
+
 msgid "Cancel"
 msgstr ""
 
@@ -513,6 +519,9 @@ msgstr ""
 msgid "Failed to suspend the system"
 msgstr ""
 
+msgid "Failed to return to desktop"
+msgstr ""
+
 msgid "just now"
 msgstr ""
 
diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo
index e77f989564c594bcedbd8f64b5181f4e516b4f15..9754ccf66dbf04c56325edd90418f01edc521f5e 100644
GIT binary patch
delta 2924
zcmaLYe@xVM9LMpG0{%b{kWleQd{OWho*W1w%%~2_Uo=iIHFM2F&qCRqa))#N*cs&1
z>2xOchuRNIm&-JY$f4P+r8c#8*B_>BW>)JDom$$mwWU2@+-LPqi=E%c=hyf1d4JxY
z&z&#5@Yb@(cLNf48~*n5Zy5jbl2rTmZ+x;b`BYul2kWpuF2r<Pj{R^Qrr>5AjJr|u
z9ku=EaT@h+Z~^A<(oAC_W(|ejG@Qmf{1Ua`9n^$_c_d;!_QG-2BJ4|jGNz)(wtLY{
zeI7D}*^HUkh6?x)YJ3-tVSRI!f_C(a?RbC#si!hZ3+5qBrU1JaL_Wr2>t0mS7T^$E
zgbuF7hjAZj;S)FlFJKPdmaK2mSbZcGVlLL<C~UUv`%o{O!UFsrM`KEwF+;HgRrjJY
zxZ2iVwf(12^IpT@m_Rf-np})1HI)>Y)6}9S^r6mhJ!;`stRJCXyok!kUF0~8$%qHm
z4<DjhhFMsR1F#--gw0rrYtX^=4Dzo9Vl>EKk*t^u-qP9Spo0adomHcbU?u9#tVN~1
z6(`{q)J{*KHu3}NDDR_op2co8exkKJGtwQrd6Wk2cs44+WvB(-Kpo8?WV7Z1>S(Uu
zAWUSt3TPP8WJaOhtFU@d-<p}I4Ah~<%}1@*7@?rlZ$ysAw4o+EfJ*IA+uvpTzd&8e
zOQ?yiqEdYq6<}W~B+(2(9o2Z${4-FQop1Y>q5_SqwjCRgESc?gz<%4_flA${sEMwk
zc619B$UW4~?xO-vAm5re6*WE!XJa1L;VN|S9P)m|+@PR^@1R~xBCon!sW=*kpd$BR
z0(x!#9MoC*uqQr`3TzoFkPs@c^|rso_HRVpt?k|Yod2Hgf-$e70yu!0=v^$sPSiv<
z@k|c_MP1geY-9MC%RF3o7j-vs`4Ld+r=$A)=*9@n!FJT;OyaSc^-V5?cW@7G#V2@M
z5yr3uuV5=K;v9I_{Dit24^Syi=2u1;%SL5t6zXo2*!Jl-kNVR{?B)ngz|$B}ihrXZ
zhq0|ka4L?$7SyFYj0`a+P?zjm)Fr!t`gZ(<3M|)&ALSHOKs8u~3$YyAkfG))%)?s_
z=U+o1lP_&0F1Egjdf^(Xe<;73UR;i|@MH8~A6D16S*Z6nqBih3>b;(<qV`(UxSd#q
z=WRVLKN6p4Mt=OoZKw#(A@|W-M@`U!gVh;7hT}1SQ*bX9;3Z^DlSmqsp)t4;%W)hY
zM!uouBJwdecql`e5%OG5!9gw5in=79p&}hLmj4&<SyW(Ku^JEK<M;>i%`+9Oq6NID
zJK)D!452RH8RTQ`@nBD81W^}ZWI6?%<r>sw+J<D;>_xI<KEdbkN7SYAu>Hw67qyce
z_!J&PVm3*nN#iD<7WUYB2$j)IsC8b$6xKIKC=93Jxb+fdQ~w=xsnWTsy0rzUpUf&`
zEwd0AVz!{h9kcbXQGqCJd~wWJ)O=5(0$PMRs-39&XF4hHF&B9#!d{G0iiV?7UWIyb
zF%JdMhC2HrsBza(KVa$Pvp<eR9YrxJfN97nnI*`5Hrw$Kw&Ow8H_sHu7kGeusJkb|
zFJ(DuqIIZ1cA{?g`>2fE#$MRFBtEXcbtERxJ{c9*6l)EVa#L^ngIJkKLx_SF*o3|D
zZB!=SLrri7HSRJh<u_5|hL(2x+4o>lMw36>+!S!aL1&3Sv^*SK(b1N+DJlJ*)~U8N
zJFBv1*@|FOctuk%91N@syF&g@s391@M*rflv$Ubn?*xJrg3W;?jxX#KHiw!D>l*@v
zyXFpRm{#O=irf{&r3LO%w>#Ne<Er(qYUrL|sxR!XaEjf<r7rg*mmOCUA6M-Qg<W%+
ze1TA-FC1*Da5{1ZSEUSyeh}@9ZHS(XwZ>Xv+hQ-qcI@)y=XBKMolVYo`9He`7XF+#
e>Hp%7o{V-nJldmO(GQuT#l&8Wc1Dj+Fn<H=a%=nm

delta 2704
zcmYM#4NR3)9LMp$3W9jU01>p5N0AT^yu#EV6mNhcuZgJSd<mn;9NK(YX>(qoSQfc#
z1g*rEO&HbIY*JCp!ntJ@n^DcJxs0}&IZfNl*etQWKkwOM*XMK2^PHFe`JeMV9^d>z
zL-42Auy*6`2>%}BpD$ds|NricGfSa5f{~aUWfqOI@qT;+$74DAuoe^XDb#p--Sb1Z
zlzI=Y!9TFnENG?lj^IW+&cws03D2Mgyn<o)7mmfhouhb2OcsgvVJzyohki^&np!Q!
zVLd9~U8wdi;VkC2Hz;UDr`>~Za1!-Pr~z*wKl_Kvy$R`kZ$d7rd#I$P;}rCxhh;bo
z8&MOt;tcG>B<z>WZ$lK4@n4*d$wc!I7NhPrqB<PJ416C`@ke|B!zP649x8(+uD%5|
zUOP_3KGc?6KxO161{uS~5}pQ%NA2BQ)P$wZ&8UtqqcYKhY@dCA3aB3^;x&v%i(xxp
z5^C$xa2{r%hxMrOTVlw+3f<gb@%9C3&%SpJhfpihXi9Yo>a5H`rF;R-$Azes?nEu5
z1GR-GQ7iun)&4i<b!2G!Gd37naX9%<gws$H)Syzo7Fm?-Lv6`xn1COn0vbeqc9Dzv
z-Ea=0-j=(l3`CF~isnOQ*h6JLH%NhPuoBdOwW!o?bPcw+=X+3x@fFlSZKzcDpaT31
zwW9N=t@;@?!3Zj|J{l{)DX97kBncMuQ&7i3_h2O|Wly38XhN;zAS!@2Q7bxz3b-3J
z@Cj7=K3tAxaRnx_4<0t6`X4||{5sMvXgw5ks7_!io<c?VD~`dN?)flk566)I(Krqj
zm=6_5JSwmx_k5;%o{BnCe)l}z8Ndj=|4S%npi1{(H4cqp?@@;{f3g`rTgxRApFy34
zE>x-qke^-Q;>Xc^1Zpq`br|>KF6_d0aVj@k@f?n4eyd@WW_$vl$6t`PwuybvS!hP3
z_5dnVou~}GgE|X+?*0I-rv3vGyZQP0WMK&^v(Gw@;zH_QVsI9PaF(e<n1$T4MW{oy
z4s|xRqTYsmsKB~Vd)SW(=sR41Lzs&RUMO?RQLpbdtildlgqNKu)5*U&)bsMH!4Z56
zZ(tb~@V$H#TT$)KqXvj18m(X{s$UE0{smOKiOec1QT_L$+7BQx+PIX^_aQAM80xr~
z8`|3-&c^pJA1`7CCa~d*ZH1`JY{G503)Ar`@}^llc~+{^xhON$xDhv?0z88{8>2{j
zA+A#X=TLYH71`HVj#n{&3(`XG`EJw%ucG$83#;)Y`Z1g+6v%uo#B6mq7hgne;U}mw
zG=L=0E+WaW;0T5F6lSnY9i}FH7~7Fx?L1cCFcOm$ksh_%f||I=)lVXCsGUVkbP0X<
zJ8~ZFwlje=Po_Q(IZHvSqM*~d8TDywM%vmT<Y(u&=+F(jdJIQeffOJwiZ!4HY(WKd
z6tz`@=;19?VDY3`nVF5sbT#_*{%@zCiI1c9{1mF;ZPY1GV$^8NMr}a=HSsDW`?do)
zuXY&U#(wO^2J%nQqO(KamjcvbEJ2OaiueBiKSn{P^E4_GcX2FE$_X``>deM5++T{Z
zSms=hO7%APd@sgPZ$_=O10(PwR3<)0^}m48aTKmlP=h-;z3EZi;l0-;><RDP9iKnC
z?Rr8(q(9g5=jP4JX<L_=xcKqnKy_wCRc2AZE2+w>SeCJ-s<Jq$yl%(N%$n_WTX!_p
z?R<KBp0{LWdAU~^s3`XGiz@=l$`=QUimHoOtXS0BnONv++mM{syDsIqsNM+w<*>+a
Mn+87|{4~q{13B0l%K!iX

diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
index 6e85830..1523e86 100644
--- a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
@@ -9,9 +9,9 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-09 09:53+0500\n"
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
-"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"POT-Creation-Date: 2025-06-10 10:25+0500\n"
+"PO-Revision-Date: 2025-06-10 10:24+0500\n"
+"Last-Translator: \n"
 "Language: ru_RU\n"
 "Language-Team: ru_RU <LL@li.org>\n"
 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
@@ -512,6 +512,12 @@ msgstr "Перейти в ждущий режим"
 msgid "Exit Application"
 msgstr "Выйти из приложения"
 
+msgid "Return to Desktop"
+msgstr "Вернуться на рабочий стол"
+
+msgid "portprotonqt-session-select file not found at /usr/bin/"
+msgstr "portprotonqt-session-select не найдет"
+
 msgid "Cancel"
 msgstr "Отмена"
 
@@ -524,6 +530,9 @@ msgstr "Не удалось завершить работу системы"
 msgid "Failed to suspend the system"
 msgstr "Не удалось перейти в ждущий режим"
 
+msgid "Failed to return to desktop"
+msgstr "Не удалось вернуться на рабочий стол"
+
 msgid "just now"
 msgstr "только что"
 

From b35a1b8dfeb012d1f1e37370224df8da3f360782 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 07:20:24 +0500
Subject: [PATCH 34/47] fix: prevent game card overlap in all\ display filter

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/main_window.py | 37 +++++++++++++++++++++----------------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 9660140..cb67f3c 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -274,9 +274,10 @@ class MainWindow(QMainWindow):
                 seen = set()
                 games = []
                 for game in portproton_games + steam_games:
-                    name = game[0]
-                    if name not in seen:
-                        seen.add(name)
+                    # Уникальный ключ: имя + exec_line
+                    key = (game[0], game[4])
+                    if key not in seen:
+                        seen.add(key)
                         games.append(game)
                 self.games_loaded.emit(games)
             self._load_portproton_games_async(
@@ -629,18 +630,19 @@ class MainWindow(QMainWindow):
             self.gamesListWidget.updateGeometry()
             return
 
-        # Создаем словарь текущих игр для быстрого поиска
-        current_games = {game_data[0]: game_data for game_data in games_list}
+        # Создаем словарь текущих игр с уникальным ключом (name + exec_line)
+        current_games = {(game_data[0], game_data[4]): game_data for game_data in games_list}
 
         # Проверяем, изменился ли список игр или размер карточек
-        current_game_names = set(current_games.keys())
-        cached_game_names = set(self.game_card_cache.keys())
+        current_game_keys = set(current_games.keys())
+        cached_game_keys = set(self.game_card_cache.keys())
         card_width_changed = self.card_width != getattr(self, '_last_card_width', None)
 
-        if current_game_names == cached_game_names and not card_width_changed:
+        if current_game_keys == cached_game_keys and not card_width_changed:
             # Список игр и размер карточек не изменились, обновляем только видимость
             search_text = self.searchEdit.text().strip().lower()
-            for game_name, card in self.game_card_cache.items():
+            for game_key, card in self.game_card_cache.items():
+                game_name = game_key[0]
                 card.setVisible(search_text in game_name.lower() or not search_text)
             self.loadVisibleImages()
             return
@@ -664,10 +666,11 @@ class MainWindow(QMainWindow):
         # Добавляем новые карточки и обновляем существующие
         for game_data in games_list:
             game_name = game_data[0]
+            game_key = (game_name, game_data[4])
             search_text = self.searchEdit.text().strip().lower()
             should_be_visible = search_text in game_name.lower() or not search_text
 
-            if game_name not in self.game_card_cache:
+            if game_key not in self.game_card_cache:
                 # Создаем новую карточку
                 card = GameCard(
                     *game_data,
@@ -686,24 +689,26 @@ class MainWindow(QMainWindow):
                 card.addToSteamRequested.connect(self.context_menu_manager.add_to_steam)
                 card.removeFromSteamRequested.connect(self.context_menu_manager.remove_from_steam)
                 card.openGameFolderRequested.connect(self.context_menu_manager.open_game_folder)
-                self.game_card_cache[game_name] = card
+                self.game_card_cache[game_key] = card
                 self.gamesListLayout.addWidget(card)
                 layout_changed = True
             else:
                 # Обновляем видимость существующей карточки
-                card = self.game_card_cache[game_name]
+                card = self.game_card_cache[game_key]
                 card.setVisible(should_be_visible)
 
         # Сохраняем текущий card_width
         self._last_card_width = self.card_width
 
+        # Принудительно обновляем макет
+        if layout_changed:
+            self.gamesListLayout.update()
+            self.gamesListWidget.updateGeometry()
+            self.gamesListWidget.update()
+
         # Загружаем изображения для видимых карточек
         self.loadVisibleImages()
 
-        # Обновляем геометрию только при необходимости
-        if layout_changed:
-            self.gamesListWidget.updateGeometry()
-
     def clearLayout(self, layout):
         """Удаляет все виджеты из layout."""
         while layout.count():

From 4e057c204c5b9dc455055ddfe96c67149185b457 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 07:21:23 +0500
Subject: [PATCH 35/47] chore(readme): update todo

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 1355fb2..d954823 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@
 - [ ] Добавить данные с HowLongToBeat на страницу с деталями игры (?)
 - [ ] Добавить виброотдачу на геймпаде при запуске игры (?)
 - [ ] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)
-- [ ] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63))
+- [X] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63))
 - [ ] Скопировать логику управления с D-pad на стрелки с клавиатуры
 
 ### Установка (devel)

From 7e9a0be150eaf1fd04a9261800e6c44f7fb97bfd Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 07:35:40 +0500
Subject: [PATCH 36/47] fix: restore theme tab after theme change

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/main_window.py | 38 +++++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index cb67f3c..bc0dd50 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -1235,9 +1235,13 @@ class MainWindow(QMainWindow):
                                             os.path.join(os.path.expanduser("~"), ".local", "share"))
                     state_file = os.path.join(xdg_data_home, "PortProtonQt", "state.txt")
                     os.makedirs(os.path.dirname(state_file), exist_ok=True)
-                    with open(state_file, "w", encoding="utf-8") as f:
-                        f.write("theme_tab\n")
-                    QTimer.singleShot(500, lambda: self.restart_application())
+                    try:
+                        with open(state_file, "w", encoding="utf-8") as f:
+                            f.write("theme_tab\n")
+                        logger.info(f"State saved to {state_file}")
+                        QTimer.singleShot(500, lambda: self.restart_application())
+                    except Exception as e:
+                        logger.error(f"Failed to save state to {state_file}: {e}")
                 else:
                     self.statusBar().showMessage(_("Error applying theme '{0}'").format(selected_theme), 3000)
 
@@ -1255,14 +1259,28 @@ class MainWindow(QMainWindow):
 
     def restore_state(self):
         """Восстанавливает состояние приложения после перезапуска."""
-        xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
-        state_file = os.path.join(xdg_cache_home, "PortProtonQt", "state.txt")
+        xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
+        state_file = os.path.join(xdg_data_home, "PortProtonQt", "state.txt")
+        logger.info(f"Checking for state file: {state_file}")
         if os.path.exists(state_file):
-            with open(state_file, encoding="utf-8") as f:
-                state = f.read().strip()
-                if state == "theme_tab":
-                    self.switchTab(5)
-            os.remove(state_file)
+            try:
+                with open(state_file, encoding="utf-8") as f:
+                    state = f.read().strip()
+                    logger.info(f"State file contents: '{state}'")
+                    if state == "theme_tab":
+                        logger.info("Restoring to theme tab (index 5)")
+                        if self.stackedWidget.count() > 5:
+                            self.switchTab(5)
+                        else:
+                            logger.warning("Theme tab (index 5) not available yet")
+                    else:
+                        logger.warning(f"Unexpected state value: '{state}'")
+                os.remove(state_file)
+                logger.info(f"State file {state_file} removed")
+            except Exception as e:
+                logger.error(f"Failed to read or process state file {state_file}: {e}")
+        else:
+            logger.info(f"State file {state_file} does not exist")
 
     # ЛОГИКА ДЕТАЛЬНОЙ СТРАНИЦЫ ИГРЫ
     def getColorPalette_async(self, cover_path, num_colors=5, sample_step=10, callback=None):

From b9d7fc23263a4ff00e5f9273eb67db04cbedc152 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 17:53:08 +0500
Subject: [PATCH 37/47] feat(input_manager): add dualshock 4 and dualsence
 mapping

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 51da736..9a55387 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -34,11 +34,13 @@ class MainWindowProtocol(Protocol):
     current_exec_line: str | None
     current_add_game_dialog: QDialog | None
 
-# Mapping of actions to evdev button codes, includes PlayStation, Xbox, and Switch controllers
+# Mapping of actions to evdev button codes, includes PlayStation, Xbox controllers
+# https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
+# https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
 BUTTONS = {
-    'confirm':   {ecodes.BTN_A},
-    'back':      {ecodes.BTN_B},
-    'add_game':  {ecodes.BTN_Y},
+    'confirm':   {ecodes.BTN_A, ecodes.BTN_SOUTH},
+    'back':      {ecodes.BTN_B, ecodes.BTN_EAST},
+    'add_game':  {ecodes.BTN_Y, ecodes.BTN_NORTH},
     'prev_tab':  {ecodes.BTN_TL},
     'next_tab':  {ecodes.BTN_TR},
     'confirm_stick': {ecodes.BTN_THUMBL, ecodes.BTN_THUMBR},

From 58c7541fa37ff73ffcb846fc48644287fe9c4b8c Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 18:16:08 +0500
Subject: [PATCH 38/47] feat(input_manager): rework gamepad buttons maping

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 36 ++++++++++++++++-------------------
 1 file changed, 16 insertions(+), 20 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 9a55387..ce2def4 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -34,19 +34,18 @@ class MainWindowProtocol(Protocol):
     current_exec_line: str | None
     current_add_game_dialog: QDialog | None
 
-# Mapping of actions to evdev button codes, includes PlayStation, Xbox controllers
+# Mapping of actions to evdev button codes, includes Xbox and Playstation controllers
 # https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
 # https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
 BUTTONS = {
-    'confirm':   {ecodes.BTN_A, ecodes.BTN_SOUTH},
-    'back':      {ecodes.BTN_B, ecodes.BTN_EAST},
-    'add_game':  {ecodes.BTN_Y, ecodes.BTN_NORTH},
-    'prev_tab':  {ecodes.BTN_TL},
-    'next_tab':  {ecodes.BTN_TR},
-    'confirm_stick': {ecodes.BTN_THUMBL, ecodes.BTN_THUMBR},
-    'context_menu': {ecodes.BTN_START},
-    'menu':      {ecodes.BTN_SELECT},
-    'guide':     {ecodes.BTN_MODE, ecodes.KEY_HOMEPAGE},
+    'confirm':   {ecodes.BTN_A, ecodes.BTN_SOUTH}, # A / Cross
+    'back':      {ecodes.BTN_B, ecodes.BTN_EAST},  # B / Circle
+    'add_game':  {ecodes.BTN_Y, ecodes.BTN_NORTH}, # Y / Triangle
+    'prev_tab':  {ecodes.BTN_TL},                  # LB / L1
+    'next_tab':  {ecodes.BTN_TR},                  # RB / R1
+    'context_menu': {ecodes.BTN_START},            # Start / Options
+    'menu':      {ecodes.BTN_SELECT},              # Select / Share
+    'guide':     {ecodes.BTN_MODE},                # Xbox / PS Home
 }
 
 class InputManager(QObject):
@@ -149,19 +148,19 @@ class InputManager(QObject):
 
             # Handle QMenu (context menu)
             if isinstance(popup, QMenu):
-                if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+                if button_code in BUTTONS['confirm']:
                     if popup.activeAction():
                         popup.activeAction().trigger()
                         popup.close()
                     return
-                elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
+                elif button_code in BUTTONS['back']:
                     popup.close()
                     return
                 return
 
             # Handle QComboBox
             if isinstance(focused, QComboBox):
-                if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+                if button_code in BUTTONS['confirm']:
                     focused.showPopup()
                 return
 
@@ -175,7 +174,7 @@ class InputManager(QObject):
                         break
                     parent = parent.parentWidget()
 
-                if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+                if button_code in BUTTONS['confirm']:
                     idx = focused.currentIndex()
                     if idx.isValid():
                         if combo:
@@ -221,18 +220,17 @@ class InputManager(QObject):
                     return
 
             # Game launch on detail page
-            if (button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']) and self._parent.currentDetailPage is not None and self._parent.current_add_game_dialog is None:
+            if (button_code in BUTTONS['confirm']) and self._parent.currentDetailPage is not None and self._parent.current_add_game_dialog is None:
                 if self._parent.current_exec_line:
                     self._parent.toggleGame(self._parent.current_exec_line, None)
                     return
 
             # Standard navigation
-            if button_code in BUTTONS['confirm'] or button_code in BUTTONS['confirm_stick']:
+            if button_code in BUTTONS['confirm']:
                 self._parent.activateFocusedWidget()
-            elif button_code in BUTTONS['back'] or button_code in BUTTONS['menu']:
+            elif button_code in BUTTONS['back']:
                 self._parent.goBackDetailPage(getattr(self._parent, 'currentDetailPage', None))
             elif button_code in BUTTONS['add_game']:
-                # Only open AddGameDialog if in library tab (index 0)
                 if self._parent.stackedWidget.currentIndex() == 0:
                     self._parent.openAddGameDialog()
             elif button_code in BUTTONS['prev_tab']:
@@ -791,9 +789,7 @@ class InputManager(QObject):
                     continue
                 now = time.time()
                 if event.type == ecodes.EV_KEY and event.value == 1:
-                    # Обработка кнопки Select для переключения полноэкранного режима
                     if event.code in BUTTONS['menu']:
-                        # Переключаем полноэкранный режим
                         self.toggle_fullscreen.emit(not self._is_fullscreen)
                     else:
                         self.button_pressed.emit(event.code)

From 0587cf58ed4752fda01c14caa06ecc1084805f0d Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 18:20:48 +0500
Subject: [PATCH 39/47] feat(input_manager): open system overlay by Insert
 button

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index ce2def4..e76f158 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -491,6 +491,12 @@ class InputManager(QObject):
         focused = QApplication.focusWidget()
         popup = QApplication.activePopupWidget()
 
+        # Open system overlay with Insert
+        if key == Qt.Key.Key_Insert:
+            if not popup and not isinstance(QApplication.activeWindow(), QDialog):
+                self._parent.openSystemOverlay()
+                return True
+
         # Close application with Ctrl+Q
         if key == Qt.Key.Key_Q and modifiers & Qt.KeyboardModifier.ControlModifier:
             app.quit()

From 2d7369d46c42c30e46082aef4a7cc951ead0b522 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 18:28:27 +0500
Subject: [PATCH 40/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index b56ea83..c8ccc34 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,8 +24,9 @@
 - Настройка автоматического перехода в полноэкранный режим при подключении геймпада (по умолчанию отключена)
 - Обработчики для QMenu и QComboBox при управлении геймпадом
 - Аргумент `--fullscreen` для запуска приложения в полноэкранном режиме
-- Оверлей на кнопку Xbox/PS для закрытия приложения, выключения, перезагрузки и перехода в спящий режим
+- Оверлей на кнопку  Insert или кнопку Xbox/PS на геймпаде для закрытия приложения, выключения, перезагрузки и перехода в спящий режим или между сессиями
 - [Gamescope сессия](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
+- Мапинги управления для Dualshock 4 и DualSense
 
 ### Changed
 - Обновлены все иконки

From 30a4fc6ed783e6a1e839caf51288bfbd7f0e9735 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 19:08:58 +0500
Subject: [PATCH 41/47] feat(input-manager): add haptic feedback for game
 launch with gamepad

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/input_manager.py | 61 ++++++++++++++++++++++++++++++-----
 1 file changed, 53 insertions(+), 8 deletions(-)

diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index e76f158..3640bfe 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -1,8 +1,8 @@
 import time
 import threading
 from typing import Protocol, cast
-from evdev import InputDevice, ecodes, list_devices
-import pyudev
+from evdev import InputDevice, InputEvent, ecodes, list_devices, ff
+from pyudev import Context, Monitor, MonitorObserver, Device
 from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox
 from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer
 from PySide6.QtGui import QKeyEvent
@@ -52,8 +52,7 @@ class InputManager(QObject):
     """
     Manages input from gamepads and keyboards for navigating the application interface.
     Supports gamepad hotplugging, button and axis events, and keyboard event filtering
-    for seamless UI interaction. Enables fullscreen mode when a gamepad is connected
-    and restores normal mode when disconnected.
+    for seamless UI interaction.
     """
     # Signals for gamepad events
     button_pressed = Signal(int)  # Signal for button presses
@@ -83,6 +82,7 @@ class InputManager(QObject):
         self.gamepad_thread: threading.Thread | None = None
         self.running = True
         self._is_fullscreen = read_fullscreen_config()
+        self.rumble_effect_id: int | None = None  # Store the rumble effect ID
 
         # Add variables for continuous D-pad movement
         self.dpad_timer = QTimer(self)
@@ -126,6 +126,46 @@ class InputManager(QObject):
         except Exception as e:
             logger.error(f"Error in handle_fullscreen_slot: {e}", exc_info=True)
 
+    def trigger_rumble(self, duration_ms: int = 200, strong_magnitude: int = 0x8000, weak_magnitude: int = 0x8000) -> None:
+        """Trigger a rumble effect on the gamepad if supported."""
+        if not self.gamepad:
+            return
+        try:
+            # Check if the gamepad supports force feedback
+            caps = self.gamepad.capabilities()
+            if ecodes.EV_FF not in caps or ecodes.FF_RUMBLE not in caps.get(ecodes.EV_FF, []):
+                logger.debug("Gamepad does not support force feedback or rumble")
+                return
+
+            # Create a rumble effect
+            rumble = ff.Rumble(strong_magnitude=strong_magnitude, weak_magnitude=weak_magnitude)
+            effect = ff.Effect(
+                id=-1,  # Let evdev assign an ID
+                type=ecodes.FF_RUMBLE,
+                direction=0,  # Direction (not used for rumble)
+                replay=ff.Replay(length=duration_ms, delay=0),
+                u=ff.EffectType(ff_rumble_effect=rumble)
+            )
+
+            # Upload the effect
+            self.rumble_effect_id = self.gamepad.upload_effect(effect)
+            # Play the effect
+            event = InputEvent(0, 0, ecodes.EV_FF, self.rumble_effect_id, 1)
+            self.gamepad.write_event(event)
+            # Schedule effect erasure after duration
+            QTimer.singleShot(duration_ms, self.stop_rumble)
+        except Exception as e:
+            logger.error(f"Error triggering rumble: {e}", exc_info=True)
+
+    def stop_rumble(self) -> None:
+        """Stop the rumble effect and clean up."""
+        if self.gamepad and self.rumble_effect_id is not None:
+            try:
+                self.gamepad.erase_effect(self.rumble_effect_id)
+                self.rumble_effect_id = None
+            except Exception as e:
+                logger.error(f"Error stopping rumble: {e}", exc_info=True)
+
     @Slot(int)
     def handle_button_slot(self, button_code: int) -> None:
         try:
@@ -222,6 +262,7 @@ class InputManager(QObject):
             # Game launch on detail page
             if (button_code in BUTTONS['confirm']) and self._parent.currentDetailPage is not None and self._parent.current_add_game_dialog is None:
                 if self._parent.current_exec_line:
+                    self.trigger_rumble()
                     self._parent.toggleGame(self._parent.current_exec_line, None)
                     return
 
@@ -728,17 +769,17 @@ class InputManager(QObject):
 
     def run_udev_monitor(self) -> None:
         try:
-            context = pyudev.Context()
-            monitor = pyudev.Monitor.from_netlink(context)
+            context = Context()
+            monitor = Monitor.from_netlink(context)
             monitor.filter_by(subsystem='input')
-            observer = pyudev.MonitorObserver(monitor, self.handle_udev_event)
+            observer = MonitorObserver(monitor, self.handle_udev_event)
             observer.start()
             while self.running:
                 time.sleep(1)
         except Exception as e:
             logger.error(f"Error in udev monitor: {e}", exc_info=True)
 
-    def handle_udev_event(self, action: str, device: pyudev.Device) -> None:
+    def handle_udev_event(self, action: str, device: Device) -> None:
         try:
             if action == 'add':
                 time.sleep(0.1)
@@ -746,6 +787,7 @@ class InputManager(QObject):
             elif action == 'remove' and self.gamepad:
                 if not any(self.gamepad.path == path for path in list_devices()):
                     logger.info("Gamepad disconnected")
+                    self.stop_rumble()
                     self.gamepad = None
                     if self.gamepad_thread:
                         self.gamepad_thread.join()
@@ -759,6 +801,7 @@ 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.stop_rumble()
                 self.gamepad = new_gamepad
                 if self.gamepad_thread:
                     self.gamepad_thread.join()
@@ -811,6 +854,7 @@ class InputManager(QObject):
         finally:
             if self.gamepad:
                 try:
+                    self.stop_rumble()
                     self.gamepad.close()
                 except Exception:
                     pass
@@ -820,6 +864,7 @@ class InputManager(QObject):
         try:
             self.running = False
             self.dpad_timer.stop()
+            self.stop_rumble()
             if self.gamepad_thread:
                 self.gamepad_thread.join()
             if self.gamepad:

From 24ca66a1afe55e5db627a19112d80c83840cddeb Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 19:09:56 +0500
Subject: [PATCH 42/47] chore(readme): update todo

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index d954823..b9e5c25 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@
 - [ ] Добавить поддержку GOG (?)
 - [X] Определиться с названием (PortProtonQt или PortProtonQT или вообще третий вариант)
 - [ ] Добавить данные с HowLongToBeat на страницу с деталями игры (?)
-- [ ] Добавить виброотдачу на геймпаде при запуске игры (?)
+- [X] Добавить виброотдачу на геймпаде при запуске игры
 - [ ] Исправить некорректную работу слайдера увеличения размера карточек([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63)
 - [X] Исправить баг с наложением карточек друг на друга при изменении фильтра отображения ([Последствия регрессии после этого коммита](https://github.com/Boria138/PortProtonQt/commit/aebdd60b5537280f06a922ff80469cd4ab27bc63))
 - [ ] Скопировать логику управления с D-pad на стрелки с клавиатуры

From 953e4fa71549eb519a342e2e3b0998ec4380b501 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 19:11:56 +0500
Subject: [PATCH 43/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c8ccc34..dd9f3fb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,6 +27,7 @@
 - Оверлей на кнопку  Insert или кнопку Xbox/PS на геймпаде для закрытия приложения, выключения, перезагрузки и перехода в спящий режим или между сессиями
 - [Gamescope сессия](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
 - Мапинги управления для Dualshock 4 и DualSense
+- Виброотдача на геймпаде при запуске игры
 
 ### Changed
 - Обновлены все иконки

From c1b8eac127a74ea622871e5d06cca789dec92b60 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 23:05:27 +0500
Subject: [PATCH 44/47] feat: add gamepad haptic feedback setting

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/config_utils.py  | 38 ++++++++++++++++++++++++++++++++---
 portprotonqt/input_manager.py |  4 +++-
 portprotonqt/main_window.py   | 17 +++++++++++++++-
 3 files changed, 54 insertions(+), 5 deletions(-)

diff --git a/portprotonqt/config_utils.py b/portprotonqt/config_utils.py
index cf40558..2066d2c 100644
--- a/portprotonqt/config_utils.py
+++ b/portprotonqt/config_utils.py
@@ -322,6 +322,41 @@ def save_favorites(favorites):
     with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
         cp.write(configfile)
 
+def read_rumble_config():
+    """
+    Читает настройку виброотдачи геймпада из секции [Gamepad].
+    Если параметр отсутствует, сохраняет и возвращает False по умолчанию.
+    """
+    cp = configparser.ConfigParser()
+    if os.path.exists(CONFIG_FILE):
+        try:
+            cp.read(CONFIG_FILE, encoding="utf-8")
+        except Exception as e:
+            logger.error("Ошибка чтения конфигурационного файла: %s", e)
+            save_rumble_config(False)
+            return False
+        if not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "rumble_enabled"):
+            save_rumble_config(False)
+            return False
+        return cp.getboolean("Gamepad", "rumble_enabled", fallback=False)
+    return False
+
+def save_rumble_config(rumble_enabled):
+    """
+    Сохраняет настройку виброотдачи геймпада в секцию [Gamepad].
+    """
+    cp = configparser.ConfigParser()
+    if os.path.exists(CONFIG_FILE):
+        try:
+            cp.read(CONFIG_FILE, encoding="utf-8")
+        except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
+            logger.error("Ошибка чтения конфигурационного файла: %s", e)
+    if "Gamepad" not in cp:
+        cp["Gamepad"] = {}
+    cp["Gamepad"]["rumble_enabled"] = str(rumble_enabled)
+    with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
+        cp.write(configfile)
+
 def ensure_default_proxy_config():
     """
     Проверяет наличие секции [Proxy] в конфигурационном файле.
@@ -342,7 +377,6 @@ def ensure_default_proxy_config():
             with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
                 cp.write(configfile)
 
-
 def read_proxy_config():
     """
     Читает настройки прокси из секции [Proxy] конфигурационного файла.
@@ -421,8 +455,6 @@ def save_fullscreen_config(fullscreen):
     with open(CONFIG_FILE, "w", encoding="utf-8") as configfile:
         cp.write(configfile)
 
-
-
 def read_window_geometry() -> tuple[int, int]:
     """
     Читает ширину и высоту окна из секции [MainWindow] конфигурационного файла.
diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py
index 3640bfe..9713485 100644
--- a/portprotonqt/input_manager.py
+++ b/portprotonqt/input_manager.py
@@ -10,7 +10,7 @@ from portprotonqt.logger import get_logger
 from portprotonqt.image_utils import FullscreenDialog
 from portprotonqt.custom_widgets import NavLabel
 from portprotonqt.game_card import GameCard
-from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad
+from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config
 
 logger = get_logger(__name__)
 
@@ -128,6 +128,8 @@ class InputManager(QObject):
 
     def trigger_rumble(self, duration_ms: int = 200, strong_magnitude: int = 0x8000, weak_magnitude: int = 0x8000) -> None:
         """Trigger a rumble effect on the gamepad if supported."""
+        if not read_rumble_config():
+            return
         if not self.gamepad:
             return
         try:
diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index bc0dd50..46e0414 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -26,7 +26,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
+    clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config
 )
 from portprotonqt.localization import _
 from portprotonqt.logger import get_logger
@@ -997,6 +997,17 @@ class MainWindow(QMainWindow):
         self.autoFullscreenGamepadCheckBox.setChecked(current_auto_fullscreen)
         formLayout.addRow(self.autoFullscreenGamepadTitle, self.autoFullscreenGamepadCheckBox)
 
+        # 7. Gamepad haptic feedback config
+        self.gamepadRumbleCheckBox = QCheckBox(_("Gamepad haptic feedback"))
+        self.gamepadRumbleCheckBox.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
+        self.gamepadRumbleCheckBox.setStyleSheet(self.theme.SETTINGS_CHECKBOX_STYLE)
+        self.gamepadRumbleTitle = QLabel(_("Gamepad haptic feedback:"))
+        self.gamepadRumbleTitle.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
+        self.gamepadRumbleTitle.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+        current_rumble_state = read_rumble_config()
+        self.gamepadRumbleCheckBox.setChecked(current_rumble_state)
+        formLayout.addRow(self.gamepadRumbleTitle, self.gamepadRumbleCheckBox)
+
         layout.addLayout(formLayout)
 
         # Кнопки
@@ -1117,6 +1128,10 @@ class MainWindow(QMainWindow):
         auto_fullscreen_gamepad = self.autoFullscreenGamepadCheckBox.isChecked()
         save_auto_fullscreen_gamepad(auto_fullscreen_gamepad)
 
+        # Сохранение настройки виброотдачи геймпада
+        rumble_enabled = self.gamepadRumbleCheckBox.isChecked()
+        save_rumble_config(rumble_enabled)
+
         for card in self.game_card_cache.values():
             card.update_badge_visibility(filter_key)
 

From 9fe5a8315a94c143774ad526b3c9b56465746499 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 23:13:50 +0500
Subject: [PATCH 45/47] chore(localization): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 documentation/localization_guide/README.md    |   6 +++---
 documentation/localization_guide/README.ru.md |   6 +++---
 .../locales/de_DE/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/de_DE/LC_MESSAGES/messages.po     |   8 +++++++-
 .../locales/es_ES/LC_MESSAGES/messages.mo     | Bin 451 -> 451 bytes
 .../locales/es_ES/LC_MESSAGES/messages.po     |   8 +++++++-
 portprotonqt/locales/messages.pot             |   8 +++++++-
 .../locales/ru_RU/LC_MESSAGES/messages.mo     | Bin 13603 -> 13827 bytes
 .../locales/ru_RU/LC_MESSAGES/messages.po     |  10 ++++++++--
 9 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/documentation/localization_guide/README.md b/documentation/localization_guide/README.md
index f4eea9e..08dd168 100644
--- a/documentation/localization_guide/README.md
+++ b/documentation/localization_guide/README.md
@@ -20,9 +20,9 @@ Current translation status:
 
 | Locale | Progress | Translated |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 160 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 160 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 160 of 160 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 162 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 162 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 162 of 162 |
 
 ---
 
diff --git a/documentation/localization_guide/README.ru.md b/documentation/localization_guide/README.ru.md
index eabcaa1..a8efde8 100644
--- a/documentation/localization_guide/README.ru.md
+++ b/documentation/localization_guide/README.ru.md
@@ -20,9 +20,9 @@
 
 | Локаль | Прогресс | Переведено |
 | :----- | -------: | ---------: |
-| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 160 |
-| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 160 |
-| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 160 из 160 |
+| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 162 |
+| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 162 |
+| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 162 из 162 |
 
 ---
 
diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.mo
index 99bd71e23efe2b7b8f78ec83ad24c916681b9a98..af709464842873b037a5e514e9c58c4958782eed 100644
GIT binary patch
delta 17
YcmX@ie3*H{L^eYOBV#MWjnj1)0W@0$N&o-=

delta 17
YcmX@ie3*H{L^cBjLjx<Ljnj1)0W>)UMgRZ+

diff --git a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
index c9e17da..14a21f6 100644
--- a/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/de_DE/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-10 10:25+0500\n"
+"POT-Creation-Date: 2025-06-11 23:15+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: de_DE\n"
@@ -376,6 +376,12 @@ msgstr ""
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr ""
 
+msgid "Gamepad haptic feedback"
+msgstr ""
+
+msgid "Gamepad haptic feedback:"
+msgstr ""
+
 msgid "Save Settings"
 msgstr ""
 
diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.mo
index 23cb19531997d3eb614aa540c54d1360dc6df01f..e423857d91e5d9befc79d7b3a117c35c1bcbaab3 100644
GIT binary patch
delta 17
YcmX@ie3*H{L^eYOBV#MWjnj1)0W@0$N&o-=

delta 17
YcmX@ie3*H{L^cBjLjx<Ljnj1)0W>)UMgRZ+

diff --git a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
index f327b4e..e139416 100644
--- a/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/es_ES/LC_MESSAGES/messages.po
@@ -9,7 +9,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-10 10:25+0500\n"
+"POT-Creation-Date: 2025-06-11 23:15+0500\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language: es_ES\n"
@@ -376,6 +376,12 @@ msgstr ""
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr ""
 
+msgid "Gamepad haptic feedback"
+msgstr ""
+
+msgid "Gamepad haptic feedback:"
+msgstr ""
+
 msgid "Save Settings"
 msgstr ""
 
diff --git a/portprotonqt/locales/messages.pot b/portprotonqt/locales/messages.pot
index 836a96d..10519af 100644
--- a/portprotonqt/locales/messages.pot
+++ b/portprotonqt/locales/messages.pot
@@ -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-06-10 10:25+0500\n"
+"POT-Creation-Date: 2025-06-11 23:15+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"
@@ -374,6 +374,12 @@ msgstr ""
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr ""
 
+msgid "Gamepad haptic feedback"
+msgstr ""
+
+msgid "Gamepad haptic feedback:"
+msgstr ""
+
 msgid "Save Settings"
 msgstr ""
 
diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.mo
index 9754ccf66dbf04c56325edd90418f01edc521f5e..e07321607e18623fac7927157ced22dd782bcf40 100644
GIT binary patch
delta 2885
zcmbW%dra0<9LMo5VxTA}cnJjK7pRDM0TELqub9mnDyT)q0=%T4A=>C-J&L+E(W!G}
zqPEg>*w&_^s0q1Mvx{ZFXBOR7x;F9;tx?WgORM+C=WMO5zhV#1>)d|le9!ls-*a%q
z#>&9=ah>apzk~cI@jvPw)&BXL8D%z*>MZPnYcK}aVNa~XZnzJ-V<X1nNz{B7UH>(l
zM*S8R;{+a>ZWgcx3XwFl;{f~#wO|~xX~Hplb;hX}fzzFHu`BgQFdEBTdlhC<uR*4=
zM(l+rPyv648viv8W_`O#K|AV6t2&bT>Wjlr3+5w3YzE$45cyb{t5>0t_8j)Z_2}VV
zyboJY3xA6#cpa0m2a6`NzKx)eiUoK-uE9ZA@7h~X4_v_vv>s+@FccH9$knS*8GOan
zTU~!UYThuS=#QzWqnU&OrKW@eb6YiP!VRc1+>ctg+1ZAA@HbROdc~S?oaUhd%fQ~a
z5bwoG)I6K85AMe}Y(gFB`B?IwL*W7q9){7Y1yh`pk!)ER>MX0!!*!^g?nfQTyQn+#
z3F;_5$I<u=YUi=+QX9)g9c=+B6H9xMe?72?2Dt@^#de?~KY$AGENX#YP)GGUk_79|
zc6C(4Q9GWG3TP!V#A;B_)j4;e-lDyz47?Gbpn*qF3$~(;q76AVYe!9Z6P4<}Tz?e#
zRDS~MlBT2P$wWSu&zAx$Ms279b#yPF=5Ih{HgLoZIE9MzBi9i`vS*iF|F5pSGx<~M
z;!qQ1qINU^70AP=ofV)0FG9^*f*QXR=i<{?sQ3Q^3LXuK<WCdip%$KkdT;^ivXx*O
zR-hu^h2i+R>pz4#!sFNpPon}miwfj@RA3if|E0VAoPQeyU9QV+z%^$FDuA1)iSFP7
z7{NJdqVae>j6hMBHj0-&0f*o)%tzggYE<g?xc+9$r0!!8>suICQ@3*gU(aAQzKPec
z4qxVJ1(-tKYjHSk#AC=Dmcy@%?#3KciXTH|Yy~P)HK@C>*|qP%h13sYfKAz-6ozA8
zwxJYHb*{t*sn_FRyo9=Rw~<GzyBE4-8K_H^hk83^qXMf&9pyGuK(C=P_!f@Ec8~LC
zE=%Sm9e@+C02kwUJmI{7dSE2KpX#r~IoN_TFq%~-<D;l?`%urfp*GNm$@N?zs{J5p
z+z$f+oF9dxfuV*cP!lzv9{dg!U?K;}J+*PD3Fe{Bcqb0UbC`$MF$2>{BWv4y)NA-G
zuEs4m1aIR*m=++f(<rP$UTtee`s@NKWjC-4Ej_ewITlepfC~H;j=*uusz57nCf4C3
z{0ez<tshZo{!G-Jn2Pf-P)I?y@h~#hzC<={o!C|u4o01AG3s(vB3ZZRQAhSFmf(A+
zOO`@f#^7kwMxMf1xEtAvwWG$xaN$@tU?~*TP>4$LGK|3&u{&<V{@CC=jS1A-P?xL&
zb&0!?2Hr$VMPji$WG!2c8n@fkkD~&)f=R4zT?s=I4nk#OB40YY)#zb8GS-?=0se@}
z&>d9DQ%Ms=n}P~pE$S|8Lyh|w^%its42F*k9Yq`_vA(5I;213*x#+eE8*v97!m-(*
z1ukP3>ODt=E@=YlOc$X7S&h21J5d?=3?uL=_Qs!`;W?pS&Uk!0j*cV>as(>nlih#<
zyn8gL1(snXzJ$uecGPo+QRB{_QvNAw+#gNpQE8Dwt4bGpPnK4#U0&`jU9x0xS!sEN
zO)Fiw<UiVTn`Xpzi;T$fMrP+`?OGI{5*aGyG;NNb*CTMiKkC07to2X(XM<b)R{v;l
zyXU{-9}BLhSnC>s8~o$J?f$9YR!=RS-{hb4&-g9=Ii5b@pEUpfP80L5d2*Y+NSht~
EH;6rVhX4Qo

delta 2665
zcmYM#eN5F=9LMpm@^BG(lt>=q@{EE8NbxYVx>SPFK(I7p4KZs&S98q9Q1@#vrcOs=
zVt=STP_|fv(U>u^HETglt<C-*Y%{a^OQx2z?4P9fhkLfz_4hjGcb>lId(OS~?0&D^
z`*~UDapP~0e`);72~+L=zoJO9T&k-u0-G@gTQClH;Zod>QFsWG@HlF|Gp_w2uBZMZ
zZo>=)Z7}n!i-M0AlbD0wp%%P@nlOn+DCS}~u5^~*66zHgjRE(*5sRsBL8h=nn1BPQ
zfQL~1C$NC^?HdZ((I2j19`C0fO)o8&gS@grTv!nKSisdAQAyi|DcFjB+=ma~Db&K}
zFbgkX2HuveZ?UYNjSpfbHe(*{b?;B12As!2`~~wdD%LC&%TV=3R0ca;{Y}?CiJJF1
zreg@v=x8$0Q)<>xU`}g7O}HI(h6hj!zv288HE<f0kvZfz&EkWBEyeq&mSZB;<1%bR
z9pPSl2)odaqw(Zl3tZ-f{1eHF#WPB0mw|pPMD45|bp(4*cV-_d^*#779!Bl-JZdAq
zqK@(&YUhdUR{hsFD-*nh;O!A!XvdpS5w@cicn5VfL&#?B66$EK;&KdSy9y``d1ZO1
zaaGO$>RYn`m4RkdzpbeCc6$_*`d;LCYydUkX;f;@xb_Lx{w?ZK&Y&i`hD!AuD!?UF
zNTQ{nj;aVXe;q2bTV4B$s6f3=*YFyWCF^q?2HpEHRO-G&O>_;lqg$vz?xJ>f4;6R_
z`PRhIsQ!t#33IR+UqwG&K*oDEOF;|YK@AKeuew~(n2#x_$O9OHjjnw&>MXb8Vtfe|
zSUW0^4pd+VTzj``??v6MzJ+$q|HMMU>}^y4r%@Aqgynb^HPKC+T123z%Q}&4#>cMk
zScP+_yOGI{fKp$JYTt>)=;3A@MP1G?9-XXjnG`<66L<ulV6-B<jNNz@d$5&rV66R)
zx*PMT6i4zaql_h^GL?tA8)fc&EpDOyG!naw;A)&iPbvP3f=pvuYw>X`z;4u~97ej>
zIn*Wl33bV4QQwY#QGsRpgGX773aA0gu>~t}0O@MqV-DW(bN&q!68O@t!yV3dQ3I}{
z+Ee-6Y{Xr-5kJQOMzFg2ZA6XlMQz|~)VRg0qW4Xxe*IX37hOFz*9%Tmmm3^-6cynG
z<UZOB)C7w-Se@~sScETQHJ-#moI%#KP|~Oj72qDM#FaRVd_!#-`PeKEWhlWT&utX^
zsD*k^m*gu{q{~<E{{lXT3hW5h<1jvk{~+Hyt6~)`(1^MNJFy8nP?v8C`Pf|^?8&l-
zx&*yi3OdU!)MYw~WY<n2S+XzidHfA^sRC@j0-r(c<aK-s$B~#Vj5Mj=YSh93SMNY&
z^dM@Tw=jzJZG=KPFFte5U^4Z;QI{%?tEyXDi2BK_LDsSsq>CL!^&5BfA5eiPZG3TT
z1!}%0Q3180j;bGZ|LiOUJ~qum5r)%CDN0AByaqLJ2M+}>fI9mTRKFXjAFw#`8H3rV
zqbNlMupT)jdjYx6)`vqlitn?&JzE-FU>+l=7q1Ck%1YEk`%!`Pqi**nsEpjkaP*Z0
z`^7l3F@*OOsKBb74M@tZ&9%RRYZG|UK|u=~L?3>D%EZU038ql}uAowW6V)$utS&O&
jH#Qg_?h7yWmlRi(9vex@@&$`!W7A1Zv18)}_0j(Ug7E$x

diff --git a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
index 1523e86..a26b290 100644
--- a/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
+++ b/portprotonqt/locales/ru_RU/LC_MESSAGES/messages.po
@@ -9,8 +9,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PROJECT VERSION\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2025-06-10 10:25+0500\n"
-"PO-Revision-Date: 2025-06-10 10:24+0500\n"
+"POT-Creation-Date: 2025-06-11 23:15+0500\n"
+"PO-Revision-Date: 2025-06-11 23:15+0500\n"
 "Last-Translator: \n"
 "Language: ru_RU\n"
 "Language-Team: ru_RU <LL@li.org>\n"
@@ -383,6 +383,12 @@ msgstr "Режим полноэкранного отображения прил
 msgid "Auto Fullscreen on Gamepad connected:"
 msgstr "Режим полноэкранного отображения приложения при подключении геймпада:"
 
+msgid "Gamepad haptic feedback"
+msgstr "Тактильная обратная связь на геймпаде"
+
+msgid "Gamepad haptic feedback:"
+msgstr "Тактильная обратная связь на геймпаде:"
+
 msgid "Save Settings"
 msgstr "Сохранить настройки"
 

From 84708ed2600f245e737813073d18ac114f7a345f Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Wed, 11 Jun 2025 23:19:31 +0500
Subject: [PATCH 46/47] chore(changelog): update

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index dd9f3fb..1de1388 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,7 +27,7 @@
 - Оверлей на кнопку  Insert или кнопку Xbox/PS на геймпаде для закрытия приложения, выключения, перезагрузки и перехода в спящий режим или между сессиями
 - [Gamescope сессия](https://git.linux-gaming.ru/Boria138/gamescope-session-portprotonqt)
 - Мапинги управления для Dualshock 4 и DualSense
-- Виброотдача на геймпаде при запуске игры
+- Настройка тактильной обратной связи на геймпаде при запуске игры (по умолчанию отключена)
 
 ### Changed
 - Обновлены все иконки

From dbf1340f8859f0cbdbf2b8cd404fdb5ccc282ac3 Mon Sep 17 00:00:00 2001
From: Boris Yumankulov <boria138@altlinux.org>
Date: Thu, 12 Jun 2025 14:37:03 +0500
Subject: [PATCH 47/47] feat: added colors to AreWeAntiCheatYet badges

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
---
 portprotonqt/game_card.py                    |  2 +-
 portprotonqt/main_window.py                  |  2 +-
 portprotonqt/themes/standart-light/styles.py | 20 +++++++++++++++++++
 portprotonqt/themes/standart/styles.py       | 21 ++++++++++++++++++++
 4 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/portprotonqt/game_card.py b/portprotonqt/game_card.py
index 42b558a..8ad4a20 100644
--- a/portprotonqt/game_card.py
+++ b/portprotonqt/game_card.py
@@ -199,7 +199,7 @@ class GameCard(QFrame):
                 icon_size=16,
                 icon_space=3,
             )
-            self.anticheatLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
+            self.anticheatLabel.setStyleSheet(self.theme.get_anticheat_badge_style(anticheat_status))
             self.anticheatLabel.setFixedWidth(int(card_width * 2/3))
             anticheat_visible = True
         else:
diff --git a/portprotonqt/main_window.py b/portprotonqt/main_window.py
index 46e0414..23af778 100644
--- a/portprotonqt/main_window.py
+++ b/portprotonqt/main_window.py
@@ -1492,7 +1492,7 @@ class MainWindow(QMainWindow):
                 icon_size=16,
                 icon_space=3,
             )
-            anticheatLabel.setStyleSheet(self.theme.STEAM_BADGE_STYLE)
+            anticheatLabel.setStyleSheet(self.theme.get_anticheat_badge_style(anticheat_status))
             anticheatLabel.setFixedWidth(badge_width)
             anticheatLabel.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(f"https://areweanticheatyet.com/game/{name.lower().replace(' ', '-')}")))
             anticheat_visible = True
diff --git a/portprotonqt/themes/standart-light/styles.py b/portprotonqt/themes/standart-light/styles.py
index 84d5da4..83f2435 100644
--- a/portprotonqt/themes/standart-light/styles.py
+++ b/portprotonqt/themes/standart-light/styles.py
@@ -416,6 +416,26 @@ def get_protondb_badge_style(tier):
         font-weight: bold;
     """
 
+def get_anticheat_badge_style(status):
+    status = status.lower()
+    status_colors = {
+        "supported": {"background": "rgba(102, 168, 15, 0.7)", "color": "black"},
+        "running": {"background": "rgba(25, 113, 194, 0.7)", "color": "black"},
+        "planned": {"background": "rgba(156, 54, 181, 0.7)", "color": "black"},
+        "broken": {"background": "rgba(232, 89, 12, 0.7)", "color": "black"},
+        "denied": {"background": "rgba(224, 49, 49, 0.7)", "color": "black"}
+    }
+    colors = status_colors.get(status, {"background": "rgba(0, 0, 0, 0.5)", "color": "white"})
+    return f"""
+        qproperty-alignment: AlignCenter;
+        background-color: {colors["background"]};
+        color: {colors["color"]};
+        font-size: 14px;
+        border-radius: 5px;
+        font-family: 'Poppins';
+        font-weight: bold;
+    """
+
 # СТИЛИ БЕЙДЖА STEAM
 STEAM_BADGE_STYLE= """
     qproperty-alignment: AlignCenter;
diff --git a/portprotonqt/themes/standart/styles.py b/portprotonqt/themes/standart/styles.py
index 2ba0c9e..bcc6a27 100644
--- a/portprotonqt/themes/standart/styles.py
+++ b/portprotonqt/themes/standart/styles.py
@@ -478,6 +478,27 @@ def get_protondb_badge_style(tier):
         font-weight: bold;
     """
 
+# СТИЛИ БЕЙДЖА WEANTICHEATYET
+def get_anticheat_badge_style(status):
+    status = status.lower()
+    status_colors = {
+        "supported": {"background": "rgba(102, 168, 15, 0.7)", "color": "black"},
+        "running": {"background": "rgba(25, 113, 194, 0.7)", "color": "black"},
+        "planned": {"background": "rgba(156, 54, 181, 0.7)", "color": "black"},
+        "broken": {"background": "rgba(232, 89, 12, 0.7)", "color": "black"},
+        "denied": {"background": "rgba(224, 49, 49, 0.7)", "color": "black"}
+    }
+    colors = status_colors.get(status, {"background": "rgba(0, 0, 0, 0.5)", "color": "white"})
+    return f"""
+        qproperty-alignment: AlignCenter;
+        background-color: {colors["background"]};
+        color: {colors["color"]};
+        font-size: 16px;
+        border-radius: 5px;
+        font-family: 'Play';
+        font-weight: bold;
+    """
+
 # СТИЛИ БЕЙДЖА STEAM
 STEAM_BADGE_STYLE= """
     qproperty-alignment: AlignCenter;