forked from CastroFidel/winehelper
added a gui tray
This commit is contained in:
@@ -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
|
||||||
|
Reference in New Issue
Block a user