added a gui tray

This commit is contained in:
Sergey Palcheh
2025-09-28 21:26:39 +06:00
parent 0f8f192634
commit 553d427d66

View File

@@ -12,9 +12,9 @@ import hashlib
from functools import partial from functools import partial
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTabWidget, QTabBar, from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTabWidget, QTabBar,
QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox, QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox,
QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox) QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox, QSystemTrayIcon, QMenu)
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QDesktopServices from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor
from PyQt5.QtNetwork import QLocalServer, QLocalSocket from PyQt5.QtNetwork import QLocalServer, QLocalSocket
@@ -1581,6 +1581,7 @@ class WineHelperGUI(QMainWindow):
self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке
self.prefixes_before_install = set() self.prefixes_before_install = set()
self.is_quitting = False # Флаг для корректного выхода из приложения
self.command_output_buffer = "" self.command_output_buffer = ""
self.command_last_line_was_progress = False self.command_last_line_was_progress = False
# Создаем главный виджет и layout # Создаем главный виджет и layout
@@ -1647,6 +1648,39 @@ class WineHelperGUI(QMainWindow):
self.raise_() self.raise_()
self.activateWindow() self.activateWindow()
def create_tray_icon(self):
"""Создает и настраивает иконку в системном трее."""
if not QSystemTrayIcon.isSystemTrayAvailable():
print("Системный трей не доступен.")
return
self.tray_icon = QSystemTrayIcon(self)
icon_path = Var.WH_ICON_PATH
if icon_path and os.path.exists(icon_path):
pixmap = QPixmap(icon_path)
if not pixmap.isNull():
self.tray_icon.setIcon(QIcon(pixmap))
# Создаем и сохраняем меню как атрибут класса, чтобы оно не удалялось
self.tray_menu = QMenu(self)
quit_action = self.tray_menu.addAction("Выход")
quit_action.triggered.connect(self.quit_application)
self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.tray_icon.show()
def on_tray_icon_activated(self, reason):
"""Обрабатывает клики по иконке в трее."""
# Показываем меню при левом клике
if reason == QSystemTrayIcon.Trigger:
# Получаем позицию курсора и показываем меню
self.tray_menu.popup(QCursor.pos())
def toggle_visibility(self):
"""Переключает видимость главного окна."""
self.setVisible(not self.isVisible())
def add_tab(self, widget, title): def add_tab(self, widget, title):
"""Добавляет вкладку в кастомный TabBar и страницу в StackedWidget.""" """Добавляет вкладку в кастомный TabBar и страницу в StackedWidget."""
self.tab_bar.addTab(title) self.tab_bar.addTab(title)
@@ -3741,8 +3775,14 @@ class WineHelperGUI(QMainWindow):
QMessageBox.critical(self, "Ошибка", QMessageBox.critical(self, "Ошибка",
f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}") f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}")
def quit_application(self):
"""Инициирует процесс выхода из приложения."""
self.is_quitting = True
self.close() # Инициируем событие закрытия, которое будет обработано в closeEvent
def closeEvent(self, event): def closeEvent(self, event):
"""Обрабатывает событие закрытия главного окна.""" """Обрабатывает событие закрытия главного окна."""
# Теперь любое закрытие окна (крестик или выход из меню) инициирует выход
if self.running_apps: if self.running_apps:
msg_box = QMessageBox(self) msg_box = QMessageBox(self)
msg_box.setWindowTitle('Подтверждение выхода') msg_box.setWindowTitle('Подтверждение выхода')
@@ -3758,16 +3798,27 @@ class WineHelperGUI(QMainWindow):
msg_box.exec_() msg_box.exec_()
if msg_box.clickedButton() == yes_button: if msg_box.clickedButton() == yes_button:
# Отключаем обработчики сигналов от всех запущенных процессов,
# так как мы собираемся их принудительно завершить и выйти.
# Это предотвращает ошибку RuntimeError при закрытии.
for process in self.running_apps.values():
process.finished.disconnect()
# Используем встроенную команду killall для надежного завершения всех процессов wine # Используем встроенную команду killall для надежного завершения всех процессов wine
print("Завершение всех запущенных приложений через 'winehelper killall'...") print("Завершение всех запущенных приложений через 'winehelper killall'...")
kill_proc = QProcess() # Используем subprocess.run, который дождется завершения команды
kill_proc.start(self.winehelper_path, ["killall"]) subprocess.run([self.winehelper_path, "killall"], check=False, capture_output=True)
kill_proc.waitForFinished(5000) # Даем до 5 секунд на завершение
# Принудительно дожидаемся завершения всех дочерних процессов
for process in self.running_apps.values():
process.waitForFinished(5000) # Ждем до 5 секунд
QApplication.instance().quit()
event.accept() event.accept()
else: else:
event.ignore() event.ignore()
else: else:
super().closeEvent(event) QApplication.instance().quit() # Если нет запущенных приложений, просто выходим
def uninstall_app(self): def uninstall_app(self):
"""Удаляет выбранное установленное приложение и его префикс""" """Удаляет выбранное установленное приложение и его префикс"""
@@ -4514,6 +4565,8 @@ def main():
# Сохраняем ссылку на сервер, чтобы он не был удален сборщиком мусора # Сохраняем ссылку на сервер, чтобы он не был удален сборщиком мусора
window.server = server window.server = server
window.show() window.show()
# Создаем иконку в системном трее после создания окна
window.create_tray_icon()
return app.exec_() return app.exec_()
return 1 return 1