@@ -12,7 +12,7 @@ import hashlib
from functools import partial
from PyQt5 . QtWidgets import ( QApplication , QMainWindow , QWidget , QVBoxLayout , QHBoxLayout , QPushButton , QLabel , QTabWidget , QTabBar ,
QTextEdit , QFileDialog , QMessageBox , QLineEdit , QCheckBox , QStackedWidget , QScrollArea , QFormLayout , QGroupBox , QRadioButton , QComboBox ,
QListWidget , QListWidgetItem , QGridLayout , QFrame , QDialog , QTextBrowser , QInputDialog )
QListWidget , QListWidgetItem , QGridLayout , QFrame , QDialog , QTextBrowser , QInputDialog , QDialogButtonBox )
from PyQt5 . QtCore import Qt , QProcess , QSize , QTimer , QProcessEnvironment , QPropertyAnimation , QEasingCurve
from PyQt5 . QtGui import QIcon , QFont , QTextCursor , QPixmap , QPainter , QDesktopServices
from PyQt5 . QtNetwork import QLocalServer , QLocalSocket
@@ -474,10 +474,9 @@ class WinetricksManagerDialog(QDialog):
self . log_output . setText ( self . INFO_TEXT )
main_layout . addWidget ( self . log_output )
# Кнопки управления
# Кнопки управления, выровненные по правому краю
button_layout = QHBoxLayout ( )
self . status_label = QLabel ( " Загрузка компонентов... " )
button_layout . addWidget ( self . status_label , 1 )
button_layout . addStretch ( 1 )
self . apply_button = QPushButton ( " Применить " )
self . apply_button . setEnabled ( False )
@@ -548,7 +547,6 @@ class WinetricksManagerDialog(QDialog):
def load_all_categories ( self ) :
""" Запускает загрузку всех категорий. """
self . loading_count = len ( self . categories )
self . category_statuses = { name : " загрузка... " for name in self . categories . keys ( ) }
for internal_name in self . categories . values ( ) :
self . _start_load_process ( internal_name )
@@ -602,13 +600,6 @@ class WinetricksManagerDialog(QDialog):
process . finished . connect ( partial ( self . _on_load_finished , category ) )
process . start ( self . winetricks_path , [ category , " list " ] )
def _update_status_label ( self ) :
""" Обновляет текстовую метку состояния загрузки. """
status_parts = [ ]
for name , status in self . category_statuses . items ( ) :
status_parts . append ( f " { name } : { status } " )
self . status_label . setText ( " | " . join ( status_parts ) )
def _parse_winetricks_log ( self ) :
""" Читает winetricks.log и возвращает множество установленных компонентов. """
installed_verbs = set ( )
@@ -681,22 +672,15 @@ class WinetricksManagerDialog(QDialog):
if exit_code != 0 or exit_status != QProcess . NormalExit :
error_string = process . errorString ( ) if process else " N/A "
self . _log ( f " --- Ошибка загрузки категории ' { category } ' (код: { exit_code } ) --- " , " red " )
self . category_statuses [ category_display_name ] = " ошибка "
self . _update_status_label ( ) # Показываем ошибку в статусе
self . _log ( f " --- Ошибка загрузки категории ' { category_display_name } ' (код: { exit_code } ) --- " , " red " )
if exit_status == QProcess . CrashExit :
self . _log ( " --- Процесс winetricks завершился аварийно. --- " , " red " )
# По умолчанию используется "Неизвестная ошибка", которая не очень полезна.
if error_string != " Неизвестная ошибка " :
self . _log ( f " --- Системная ошибка: { error_string } --- " , " red " )
self . _log ( output if output . strip ( ) else " Winetricks не вернул вывод. Проверьте, что он работает корректно. " )
self . _log ( " -------------------------------------------------- " , " red " )
else :
self . category_statuses [ category_display_name ] = " готово "
installed_verbs = self . _parse_winetricks_log ( )
# Обновляем статус только если это была сетевая загрузка
if from_cache is None :
self . _update_status_label ( )
found_items = self . _parse_winetricks_list_output ( output , installed_verbs , list_widget )
if from_cache is None : # Только если мы не читали из кэша
@@ -721,7 +705,6 @@ class WinetricksManagerDialog(QDialog):
self . loading_count - = 1
if self . loading_count == 0 :
self . status_label . setText ( " Готово. " )
self . _update_ui_state ( )
def _on_item_changed ( self , item ) :
@@ -862,11 +845,6 @@ class WinetricksManagerDialog(QDialog):
# 3. Обрабатываем успех
self . _log ( " \n === В с е операции успешно завершены === " )
self . _show_message_box ( " Успех " ,
" Операции с компонентами были успешно выполнены. " ,
QMessageBox . Information ,
{ " buttons " : { " Да " : QMessageBox . AcceptRole } } )
self . apply_button . setEnabled ( True )
self . reinstall_button . setEnabled ( False ) # Сбрасываем в неактивное состояние
self . close_button . setEnabled ( True )
@@ -876,7 +854,6 @@ class WinetricksManagerDialog(QDialog):
search_edit . clear ( )
# Перезагружаем данные, чтобы обновить состояние
self . status_label . setText ( " Обновление данных... " )
self . initial_states . clear ( )
self . load_all_categories ( )
self . installation_finished = True
@@ -1355,6 +1332,75 @@ class CreatePrefixDialog(QDialog):
self . accept ( )
class FileAssociationsDialog ( QDialog ) :
""" Диалог для управления ассоциациями файлов (WH_XDG_OPEN). """
def __init__ ( self , current_associations , parent = None ) :
super ( ) . __init__ ( parent )
self . setWindowTitle ( " Настройка ассоциаций файлов " )
self . setMinimumWidth ( 450 )
self . setModal ( True )
self . new_associations = current_associations
layout = QVBoxLayout ( self )
layout . setSpacing ( 10 ) # Добавляем вертикальный отступ между виджетами
info_label = QLabel (
" Укажите расширения файлов, которые должны открываться нативными<br> "
" приложениями Linux. Чтобы удалить все ассоциации, очистите поле.<br><br> "
" <b>Пример:</b> <code>pdf docx txt</code> "
)
info_label . setWordWrap ( True )
info_label . setTextFormat ( Qt . RichText )
layout . addWidget ( info_label )
self . associations_edit = QLineEdit ( )
# Если ассоциации не заданы (значение "0"), поле будет пустым, чтобы показать подсказку
if current_associations != " 0 " :
self . associations_edit . setText ( current_associations )
self . associations_edit . setPlaceholderText ( " Введите расширения через пробел... " )
layout . addWidget ( self . associations_edit )
# Запрещенные расширения
forbidden_label = QLabel (
" <small><b>Запрещено использовать:</b> cpl, dll, exe, lnk, msi</small> "
)
forbidden_label . setTextFormat ( Qt . RichText ) # Включаем обработку HTML
layout . addWidget ( forbidden_label )
button_box = QDialogButtonBox ( QDialogButtonBox . Ok | QDialogButtonBox . Cancel )
button_box . accepted . connect ( self . validate_and_accept )
button_box . rejected . connect ( self . reject )
layout . addWidget ( button_box )
def validate_and_accept ( self ) :
""" Проверяет введенные данные перед закрытием. """
forbidden_extensions = { " cpl " , " dll " , " exe " , " lnk " , " msi " }
# Получаем введенные расширения, очищаем от лишних пробелов
input_text = self . associations_edit . text ( ) . lower ( ) . strip ( )
entered_extensions = { ext . strip ( ) for ext in input_text . split ( ) if ext . strip ( ) }
found_forbidden = entered_extensions . intersection ( forbidden_extensions )
if found_forbidden :
msg_box = QMessageBox ( self )
msg_box . setIcon ( QMessageBox . Warning )
msg_box . setWindowTitle ( " Недопустимые расширения " )
msg_box . setTextFormat ( Qt . RichText )
msg_box . setText (
" Следующие расширения запрещены и не могут быть использованы:<br><br> "
f " <b> { ' , ' . join ( sorted ( list ( found_forbidden ) ) ) } </b> "
)
msg_box . exec_ ( )
return
# Сохраняем результат в виде отсортированной строки
self . new_associations = " " . join ( sorted ( list ( entered_extensions ) ) )
self . accept ( )
class ComponentVersionSelectionDialog ( QDialog ) :
""" Диалог для выбора версии компонента (DXVK, VKD3D). """
@@ -2142,61 +2188,56 @@ class WineHelperGUI(QMainWindow):
self . vkd3d_manage_button . setToolTip ( " Установка или удаление определенной версии vkd3d-proton в префиксе. " )
management_layout . addWidget ( self . vkd3d_manage_button , 5 , 1 )
# --- Правая сторона: Информационный блок ---
self . file_associations_button = QPushButton ( " Ассоциации файлов " )
self . file_associations_button . setMinimumHeight ( 32 )
self . file_associations_button . clicked . connect ( self . open_file_associations_manager )
self . file_associations_button . setToolTip (
" Настройка открытия определенных типов файлов с помощью нативных приложений Linux. " )
management_layout . addWidget ( self . file_associations_button , 6 , 0 , 1 , 2 )
# --- Правая сторона: Информационный блок и кнопки установки ---
right_column_widget = QWidget ( )
right_column_layout = QVBoxLayout ( right_column_widget )
right_column_layout . setContentsMargins ( 0 , 0 , 0 , 0 )
right_column_layout . setSpacing ( 10 )
self . prefix_info_display = QTextBrowser ( )
self . prefix_info_display . setReadOnly ( True )
self . prefix_info_display . setFrameStyle ( QFrame . StyledPanel )
# Увеличиваем rowspan, чтобы охватить все строки с кнопками
management_layout . addWidget ( self . prefix_info_display , 0 , 2 , 6 , 1 )
management_layout . setColumnStretch ( 0 , 1 )
management_layout . setColumnStretch ( 1 , 1 )
management_layout . setColumnStretch ( 2 , 2 )
separator = QFrame ( )
separator . setFrameShape ( QFrame . HLine )
separator . setFrameShadow ( QFrame . Sunken )
management_layout . addWidget ( separator , 6 , 0 , 1 , 3 )
right_column_layout . addWidget ( self . prefix_info_display )
install_group = QWidget ( )
install_layout = QVBoxLayout ( install_group )
install_layout . setContentsMargins ( 0 , 5 , 0 , 0 )
install_layout . setContentsMargins ( 0 , 0 , 0 , 0 )
install_layout . setSpacing ( 5 )
install_path_layout = QHBoxLayout ( )
self . prefix_install_path_edit = QLineEdit ( )
self . prefix_install_path_edit . setPlaceholderText ( " Укажите путь к установочному файлу .exe или .msi... " )
install_path_layout . addWidget ( self . prefix_install_path_edit )
self . prefix_browse_button = QPushButton ( " Обзор... " )
self . prefix_browse_button . clicked . connect ( self . browse_for_prefix_installer )
install_path_layout . addWidget ( self . prefix_browse_button )
install_layout . addLayout ( install_path_layout )
# Layout для кнопок установки и создания ярлыка
action_buttons_layout = QHBoxLayout ( )
self . prefix_install_button = QPushButton ( " Установить приложение в префикс " )
self . prefix_install_button . setEnabled ( False )
self . prefix_install_button . clicked . connect ( self . run_prefix_installer )
action_buttons _layout. addWidget ( self . prefix_install_button )
self . prefix_install_button . clicked . connect ( self . browse_and_ run_prefix_installer)
install _layout. addWidget ( self . prefix_install_button )
self . create_launcher_button = QPushButton ( " Создать ярлык для приложения в префиксе " )
self . create_launcher_button . setToolTip (
" Создает ярлык в меню и на вкладке ' Установленные ' для .exe файла внутри префикса. " )
self . create_launcher_button . clicked . connect ( self . create_launcher_for_prefix )
self . create_launcher_button . setEnabled ( False ) # Изначально неактивна
action_buttons _layout. addWidget ( self . create_launcher_button )
install _layout. addLayou t ( action_buttons_lay out )
management_layout . addWidget ( install_group , 7 , 0 , 1 , 3 )
install _layout. addWidget ( self . create_launcher_button )
right_column _layout. addWidge t ( install_gr oup )
right_column_layout . setStretch ( 0 , 1 ) # Информационное окно растягивается
right_column_layout . setStretch ( 1 , 0 ) # Группа кнопок не растягивается
management_layout . addWidget ( right_column_widget , 0 , 2 , 7 , 1 )
management_layout . setColumnStretch ( 0 , 1 )
management_layout . setColumnStretch ( 1 , 1 )
management_layout . setColumnStretch ( 2 , 2 )
container_layout . addWidget ( self . prefix_management_groupbox )
layout . addWidget ( self . management_container_groupbox )
layout . addStretch ( )
self . add_tab ( self . prefix_tab , " Менеджер префиксов " )
self . prefix_install_path_edit . textChanged . connect ( self . update_prefix_install_button_state )
def _get_current_prefixes ( self ) :
""" Возвращает множество имен существующих префиксов. """
prefixes_root_path = os . path . join ( Var . USER_WORK_PATH , " prefixes " )
@@ -2322,7 +2363,6 @@ class WineHelperGUI(QMainWindow):
# Успешное удаление, обновляем GUI
self . _remove_prefix_from_gui_state ( prefix_name )
self . update_installed_apps ( )
QMessageBox . information ( self , " Успех " , f " Префикс ' { prefix_name } ' и все связанные с ним данные были успешно удалены. " )
else :
QMessageBox . critical ( self , " Ошибка удаления " , f " Н е удалось удалить префикс ' { prefix_name } ' . \n Подробности смотрите в логе. " )
@@ -2331,18 +2371,16 @@ class WineHelperGUI(QMainWindow):
is_prefix_selected = bool ( prefix_name )
self . prefix_management_groupbox . setEnabled ( is_prefix_selected )
self . create_launcher_button . setEnabled ( is_prefix_selected )
self . prefix_install_button . setEnabled ( is_prefix_selected )
if is_prefix_selected :
self . update_prefix_info_display ( prefix_name )
else :
self . prefix_info_display . clear ( )
self . prefix_install_path_edit . clear ( )
# Сбрасываем состояние кнопок, когда префикс не выбран
self . esync_button . setChecked ( False )
self . fsync_button . setChecked ( False )
self . update_prefix_install_button_state ( )
def update_prefix_info_display ( self , prefix_name ) :
""" Обновляет информационный блок для созданного префикса, читая данные из last.conf. """
if not prefix_name :
@@ -2396,8 +2434,9 @@ class WineHelperGUI(QMainWindow):
" VKD3D_VER " : ( " Версия VKD3D " , lambda v : v if v else " Н е установлено" ) ,
" WINEESYNC " : ( " ESync " , lambda v : " Включен " if v == " 1 " else " Выключен " ) ,
" WINEFSYNC " : ( " FSync " , lambda v : " Включен " if v == " 1 " else " Выключен " ) ,
" WH_XDG_OPEN " : ( " Ассоциации файлов " , lambda v : v if v and v != " 0 " else " Н е заданы" ) ,
}
display_order = [ " WINEPREFIX " , " WINEARCH " , " WH_WINE_USE " , " BASE_PFX " , " DXVK_VER " , " VKD3D_VER " , " WINEESYNC " , " WINEFSYNC " ]
display_order = [ " WINEPREFIX " , " WINEARCH " , " WH_WINE_USE " , " BASE_PFX " , " DXVK_VER " , " VKD3D_VER " , " WINEESYNC " , " WINEFSYNC " , " WH_XDG_OPEN " ]
html_content = f ' <p style= " line-height: 1.3; font-size: 9pt; " > '
html_content + = f " <b>Имя:</b> { html . escape ( prefix_name ) } <br> "
@@ -2422,8 +2461,13 @@ class WineHelperGUI(QMainWindow):
html_content + = " </p> "
self . prefix_info_display . setHtml ( html_content )
def browse_for _prefix_installer ( self ) :
""" Открывает диалог выбора файла для установки в созданный префикс. """
def browse_and_run _prefix_installer ( self ) :
""" Открывает диалог выбора файла и запускает установку в созданный префикс. """
prefix_name = self . current_managed_prefix_name
if not prefix_name :
QMessageBox . warning ( self , " Ошибка " , " Сначала выберите префикс для установки. " )
return
file_path , _ = QFileDialog . getOpenFileName (
self ,
" Выберите установочный файл " ,
@@ -2431,18 +2475,11 @@ class WineHelperGUI(QMainWindow):
" Исполняемые файлы (*.exe *.msi);;В с е файлы (*) "
)
if file_path :
self . prefix_install_path_edit . setText ( file_path )
self . run_ prefix_installer ( file_path )
def update _prefix_install_button_state ( self ) :
""" Обновляет состояние кнопки установки в префикс ."""
path_ok = bool ( self . prefix_install_path_edit . text ( ) . strip ( ) )
prefix_selected = self . current_managed_prefix_name is not None
self . prefix_install_button . setEnabled ( path_ok and prefix_selected )
def run_prefix_installer ( self ) :
""" Запускает установку файла в выбранный префикс. """
def run _prefix_installer ( self , installer_path ) :
""" Запускает установку файла в выбранный префикс через скрипт winehelper ."""
prefix_name = self . current_managed_prefix_name
installer_path = self . prefix_install_path_edit . text ( ) . strip ( )
if not prefix_name :
QMessageBox . warning ( self , " Ошибка " , " Н е выбран префикс для установки." )
@@ -2451,9 +2488,6 @@ class WineHelperGUI(QMainWindow):
QMessageBox . warning ( self , " Ошибка " , " Указан неверный путь к установочному файлу. " )
return
prefix_path = os . path . join ( Var . USER_WORK_PATH , " prefixes " , prefix_name )
wine_executable = self . _get_wine_executable_for_prefix ( prefix_name )
self . command_dialog = QDialog ( self )
self . command_dialog . setWindowTitle ( f " Установка в префикс: { prefix_name } " )
self . command_dialog . setMinimumSize ( 750 , 400 )
@@ -2477,13 +2511,12 @@ class WineHelperGUI(QMainWindow):
self . command_process . readyReadStandardOutput . connect ( self . _handle_command_output )
self . command_process . finished . connect ( self . _handle_prefix_install_finished )
env = QProcessEnvironment . systemEnvironment ( )
env . insert ( " WINEPREFIX " , prefix_path )
self . command_process . setProcessEnvironment ( env )
# Окружение полностью настраивается скриптом winehelper
self . command_process . setProcessEnvironment ( QProcessEnvironment . systemEnvironment ( ) )
args = [ installer_path ]
self . command_log_output . append ( f " Запуск установки : { shlex . quote ( wine_executable ) } { shlex . quote ( installer_path ) } " )
self . command_process . start ( wine_executable , args )
args = [ " install-to-prefix " , prefix_name , installer_path ]
self . command_log_output . append ( f " Выполнение : { shlex . quote ( self . winehelper_path ) } { ' ' . join ( shlex . quote ( a ) for a in args ) } " )
self . command_process . start ( self . winehelper_path , args )
self . command_dialog . exec_ ( )
def _get_prefix_component_version ( self , prefix_name , component_key ) :
@@ -2703,6 +2736,64 @@ class WineHelperGUI(QMainWindow):
if exit_code == 0 :
self . update_prefix_info_display ( prefix_name )
def open_file_associations_manager ( self ) :
""" Открывает диалог для управления ассоциациями файлов. """
prefix_name = self . current_managed_prefix_name
if not prefix_name :
QMessageBox . warning ( self , " Ошибка " , " Сначала выберите префикс. " )
return
current_associations = self . _get_prefix_component_version ( prefix_name , " WH_XDG_OPEN " ) or " "
dialog = FileAssociationsDialog ( current_associations , self )
if dialog . exec_ ( ) == QDialog . Accepted :
new_associations = dialog . new_associations
# Запускаем обновление, только если значение изменилось
if new_associations != current_associations :
self . run_update_associations_command ( prefix_name , new_associations )
def run_update_associations_command ( self , prefix_name , new_associations ) :
""" Выполняет команду обновления ассоциаций файлов. """
prefix_path = os . path . join ( Var . USER_WORK_PATH , " prefixes " , prefix_name )
self . command_dialog = QDialog ( self )
self . command_dialog . setWindowTitle ( " Обновление ассоциаций файлов " )
self . command_dialog . setMinimumSize ( 750 , 400 )
self . command_dialog . setModal ( True )
self . command_dialog . setWindowFlags ( self . command_dialog . windowFlags ( ) & ~ Qt . WindowCloseButtonHint )
layout = QVBoxLayout ( )
self . command_log_output = QTextEdit ( )
self . command_log_output . setReadOnly ( True )
self . command_log_output . setFont ( QFont ( ' DejaVu Sans Mono ' , 10 ) )
layout . addWidget ( self . command_log_output )
self . command_close_button = QPushButton ( " Закрыть " )
self . command_close_button . setEnabled ( False )
self . command_close_button . clicked . connect ( self . command_dialog . close )
layout . addWidget ( self . command_close_button )
self . command_dialog . setLayout ( layout )
self . command_process = QProcess ( self . command_dialog )
self . command_process . setProcessChannelMode ( QProcess . MergedChannels )
self . command_process . readyReadStandardOutput . connect ( self . _handle_command_output )
self . command_process . finished . connect (
lambda exit_code , exit_status : self . _handle_component_install_finished (
prefix_name , exit_code , exit_status
)
)
env = QProcessEnvironment . systemEnvironment ( )
env . insert ( " WINEPREFIX " , prefix_path )
# Устанавливаем новую переменную окружения для скрипта
env . insert ( " WH_XDG_OPEN " , new_associations )
self . command_process . setProcessEnvironment ( env )
args = [ " init-prefix " ]
self . command_log_output . append ( f " Выполнение: { shlex . quote ( self . winehelper_path ) } { ' ' . join ( shlex . quote ( a ) for a in args ) } " )
self . command_process . start ( self . winehelper_path , args )
self . command_dialog . exec_ ( )
def create_launcher_for_prefix ( self ) :
"""
Открывает диалог для создания ярлыка для приложения внутри выбранного префикса.
@@ -2991,10 +3082,6 @@ class WineHelperGUI(QMainWindow):
if not self . management_container_groupbox . isVisible ( ) :
self . management_container_groupbox . setVisible ( True )
QMessageBox . information ( self , " Успех " ,
f " Префикс ' { prefix_name } ' успешно создан. \n "
" Теперь вы можете управлять им, выбрав е г о из выпадающего списка. " )
def update_installed_apps ( self ) :
""" Обновляет список установленных приложений в виде кнопок """
# Если активная кнопка находится в списке удаляемых, сбрасываем е е
@@ -3063,7 +3150,6 @@ class WineHelperGUI(QMainWindow):
self . command_process . deleteLater ( )
self . command_process = None
self . command_close_button . setEnabled ( True )
self . prefix_install_path_edit . clear ( )
self . update_installed_apps ( )
def _set_active_button ( self , button_widget ) :
@@ -3084,11 +3170,13 @@ class WineHelperGUI(QMainWindow):
def show_installed_app_info ( self , desktop_path , button_widget ) :
""" Показывает информацию о б установленном приложении в правой панели. """
self . _set_active_button ( button_widget )
# Очищаем поле поиска и принудительно обновляем список, чтобы показать все приложения
self . installed_search_edit . blockSignals ( True )
self . installed_search_edit . clear ( )
self . installed_search_edit . blockSignals ( Fals e)
self . filter_ installed_buttons ( )
# Если в поиске был текст, очищаем е г о и перерисовываем список.
# Это предотвращает "прыжок", если список не был отфильтрован.
if self . installed_search_edit . text ( ) :
self . installed_search_edit . blockSignals ( Tru e)
self . installed_search_edit . clear ( )
self . installed_search_edit . blockSignals ( False )
self . filter_installed_buttons ( )
# Прокручиваем к выбранному элементу
frame = button_widget . parent ( )
@@ -3538,7 +3626,7 @@ class WineHelperGUI(QMainWindow):
QMessageBox . critical ( self , " Ошибка " , f " Н е удалось модифицировать команду для отладки: { e } " )
return
process = QProcess ( self )
process = QProcess ( )
env = QProcessEnvironment . systemEnvironment ( )
cmd_start_index = 0
@@ -3556,7 +3644,10 @@ class WineHelperGUI(QMainWindow):
arguments = clean_command [ cmd_start_index + 1 : ]
process . setProcessEnvironment ( env )
process . finished . connect ( lambda : self . _on_app_process_finished ( desktop_path ) )
# Используем functools.partial для надежной передачи аргументов
# и избегания проблем с замыканием в lambda.
process . finished . connect ( partial ( self . _on_app_process_finished , desktop_path ) )
try :
process . start ( program , arguments )
@@ -3575,6 +3666,36 @@ class WineHelperGUI(QMainWindow):
QMessageBox . critical ( self , " Ошибка " ,
f " Н е удалось обработать команду запуска:\n { command_str } \n \n Ошибка: { str ( e ) } " )
def closeEvent ( self , event ) :
""" Обрабатывает событие закрытия главного окна. """
if self . running_apps :
msg_box = QMessageBox ( self )
msg_box . setWindowTitle ( ' Подтверждение выхода ' )
msg_box . setTextFormat ( Qt . RichText )
msg_box . setText ( ' <font color= " red " >В с е запущенные приложения будут закрыты вместе с WineHelper.</font><br><br> '
" Вы уверены, что хотите выйти? " )
msg_box . setIcon ( QMessageBox . Question )
yes_button = msg_box . addButton ( " Да " , QMessageBox . YesRole )
no_button = msg_box . addButton ( " Нет " , QMessageBox . NoRole )
msg_box . setDefaultButton ( no_button )
msg_box . exec_ ( )
if msg_box . clickedButton ( ) == yes_button :
# Корректно завершаем все дочерние процессы
for desktop_path , process in list ( self . running_apps . items ( ) ) :
if process . state ( ) == QProcess . Running :
print ( f " Завершение процесса для { desktop_path } ... " )
process . terminate ( )
if not process . waitForFinished ( 2000 ) : # Ждем 2 сек
process . kill ( ) # Если не закрылся, убиваем
event . accept ( )
else :
event . ignore ( )
else :
super ( ) . closeEvent ( event )
def uninstall_app ( self ) :
""" Удаляет выбранное установленное приложение и е г о префикс """
if not self . current_selected_app or ' desktop_path ' not in self . current_selected_app :
@@ -3758,11 +3879,14 @@ class WineHelperGUI(QMainWindow):
search_edit = tab_data [ ' search_edit ' ]
scroll_area = tab_data [ ' scroll_area ' ]
# Общая логика: очищаем поиск, обновляем список и прокручиваем к элементу
search_edit . blockSignals ( True )
search_edit . clear ( )
search_edit . blockSignals ( Fals e)
self . filter_buttons ( tab_type )
# Если в поиске был текст, очищаем е г о и перерисовываем список.
# Это предотвращает "прыжок", если список не был отфильтрован.
if search_edit . text ( ) :
search_edit . blockSignals ( Tru e)
search_edit . clear ( )
search_edit . blockSignals ( False )
self . filter_buttons ( tab_type )
frame = button_widget . parent ( )
if isinstance ( frame , QFrame ) :
QTimer . singleShot ( 0 , lambda : scroll_area . ensureWidgetVisible ( frame ) )
@@ -4124,18 +4248,6 @@ class WineHelperGUI(QMainWindow):
self . created_prefix_selector . setCurrentIndex ( 0 )
# --- Конец обновления списка префиксов ---
# Создаем кастомный диалог, чтобы кнопка была на русском
success_box = QMessageBox ( self . install_dialog )
success_box . setWindowTitle ( " Успех " )
title_name = self . _get_current_app_title ( )
success_text = f " Программа « { title_name } » установлена успешно! "
if new_prefix_name :
success_text + = f " \n \n Новый префикс ' { new_prefix_name } ' был автоматически выбран в списке управления на вкладке ' Менеджер префиксов ' . "
success_box . setText ( success_text )
success_box . setIcon ( QMessageBox . Information )
success_box . addButton ( " Готово " , QMessageBox . AcceptRole )
success_box . exec_ ( )
self . update_installed_apps ( )
# Кнопка закрыть
@@ -4235,7 +4347,6 @@ class WineHelperGUI(QMainWindow):
""" Обрабатывает завершение создания ярлыка. """
self . _handle_command_finished ( exit_code , exit_status )
if exit_code == 0 :
QMessageBox . information ( self , " Успех " , " Ярлык успешно создан. " )
self . update_installed_apps ( )
# Переключаемся на вкладку "Установленные"
for i in range ( self . tab_bar . count ( ) ) :