Files
PortProtonQt/portprotonqt/tray_manager.py

142 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
from PySide6.QtWidgets import QSystemTrayIcon, QMenu
from PySide6.QtGui import QIcon, QAction
from portprotonqt.logger import get_logger
from portprotonqt.theme_manager import ThemeManager
import portprotonqt.themes.standart.styles as default_styles
from portprotonqt.localization import _
from portprotonqt.config_utils import read_favorites, read_theme_from_config
logger = get_logger(__name__)
class TrayManager:
"""Модуль управления системным треем для PortProtonQt.
Обеспечивает:
- Показ/скрытие главного окна по клику на иконку трея.
- Контекстное меню с опциями: Show/Hide (переключается в зависимости от состояния окна),
Favorites (быстрый запуск избранных игр), Recent Games (быстрый запуск недавних игр), Exit.
- Меню Favorites и Recent Games динамически заполняются при показе (через aboutToShow).
- При закрытии окна (крестик) приложение сворачивается в трей, а не закрывается.
Полное закрытие только через Exit в меню.
"""
def __init__(self, main_window, app_name: str | None = None, theme=None):
self.app_name = app_name if app_name is not None else "PortProtonQt"
self.theme_manager = ThemeManager()
self.theme = theme if theme is not None else default_styles
self.current_theme_name = read_theme_from_config()
self.main_window = main_window
self.tray_icon = QSystemTrayIcon(self.main_window)
icon = self.theme_manager.get_icon("portproton", self.current_theme_name)
if isinstance(icon, str):
icon = QIcon(icon)
elif icon is None:
icon = QIcon()
self.tray_icon.setIcon(icon)
self.tray_icon.activated.connect(self.toggle_window)
self.tray_icon.setToolTip(self.app_name)
# Контекстное меню
self.tray_menu = QMenu()
self.toggle_action = QAction(_("Show"), self.main_window)
self.toggle_action.triggered.connect(self.toggle_window_action)
# Подменю для избранных игр
self.favorites_menu = QMenu(_("Favorites"))
self.favorites_menu.aboutToShow.connect(self.populate_favorites_menu)
# Подменю для недавних игр (топ-5 по последнему запуску)
self.recent_menu = QMenu(_("Recent Games"))
self.recent_menu.aboutToShow.connect(self.populate_recent_menu)
# Добавляем действия в меню
self.tray_menu.addAction(self.toggle_action)
self.tray_menu.addSeparator()
self.tray_menu.addMenu(self.favorites_menu)
self.tray_menu.addMenu(self.recent_menu)
self.tray_menu.addSeparator()
exit_action = QAction(_("Exit"), self.main_window)
exit_action.triggered.connect(self.force_exit)
self.tray_menu.addAction(exit_action)
self.tray_menu.aboutToShow.connect(self.update_toggle_action)
self.tray_icon.setContextMenu(self.tray_menu)
self.tray_icon.show()
# Флаг для принудительного выхода
self.main_window.is_exiting = False
def update_toggle_action(self):
"""Update toggle_action text based on window visibility."""
if self.main_window.isVisible():
self.toggle_action.setText(_("Hide"))
else:
self.toggle_action.setText(_("Show"))
def toggle_window(self, reason):
"""Переключает видимость окна по клику на иконку трея."""
if reason == QSystemTrayIcon.ActivationReason.Trigger:
self.toggle_window_action()
def toggle_window_action(self):
"""Toggle window visibility and update action text."""
if self.main_window.isVisible():
self.main_window.hide()
else:
self.main_window.show()
self.main_window.raise_()
self.main_window.activateWindow()
def populate_favorites_menu(self):
"""Динамически заполняет меню избранных игр с указанием источника."""
self.favorites_menu.clear()
favorites = read_favorites()
if not favorites:
no_fav_action = QAction(_("No favorites"), self.main_window)
no_fav_action.setEnabled(False)
self.favorites_menu.addAction(no_fav_action)
return
game_map = {game[0]: (game[4], game[12]) for game in self.main_window.games}
for fav in sorted(favorites):
game_data = game_map.get(fav)
if game_data:
exec_line, source = game_data
action_text = f"{fav} ({source})"
action = QAction(action_text, self.main_window)
action.triggered.connect(lambda checked=False, el=exec_line: self.main_window.toggleGame(el))
self.favorites_menu.addAction(action)
else:
logger.warning(f"Exec line not found for favorite: {fav}")
def populate_recent_menu(self):
"""Динамически заполняет меню недавних игр (топ-5 по timestamp) с указанием источника."""
self.recent_menu.clear()
if not self.main_window.games:
no_recent_action = QAction(_("No recent games"), self.main_window)
no_recent_action.setEnabled(False)
self.recent_menu.addAction(no_recent_action)
return
recent_games = sorted(self.main_window.games, key=lambda g: g[10], reverse=True)[:5]
for game in recent_games:
game_name = game[0]
exec_line = game[4]
source = game[12]
action_text = f"{game_name} ({source})"
action = QAction(action_text, self.main_window)
action.triggered.connect(lambda checked=False, el=exec_line: self.main_window.toggleGame(el))
self.recent_menu.addAction(action)
def force_exit(self):
"""Принудительно закрывает приложение."""
self.main_window.is_exiting = True
self.main_window.close()
sys.exit(0)