Merge branch 'minergenon-devel'

This commit is contained in:
Mikhail Tergoev
2025-09-25 13:10:01 +03:00
2 changed files with 217 additions and 34 deletions

View File

@@ -1184,6 +1184,16 @@ init_wineprefix () {
# добавление ассоциаций файлов для запуска нативного приложения из wine # добавление ассоциаций файлов для запуска нативного приложения из wine
# пример переменной: WH_XDG_OPEN="txt doc pdf" # пример переменной: WH_XDG_OPEN="txt doc pdf"
check_variables WH_XDG_OPEN "0" check_variables WH_XDG_OPEN "0"
# Сохраняем старые ассоциации, чтобы потом удалить те, что больше не нужны
local old_xdg_open
if [[ -f "$WINEPREFIX/last.conf" ]]; then
old_xdg_open=$(grep "WH_XDG_OPEN=" "$WINEPREFIX/last.conf" | sed -e 's/.*WH_XDG_OPEN="//' -e 's/"$//')
fi
# Если переменная WH_XDG_OPEN была установлена извне (например, из GUI),
# то мы должны принудительно установить ее значение в "0", если она пуста,
# чтобы корректно удалить старые ассоциации.
[[ -z "$WH_XDG_OPEN" ]] && WH_XDG_OPEN="0"
local WRAPPER="${WH_TMP_DIR}/wh-xdg-open.sh" local WRAPPER="${WH_TMP_DIR}/wh-xdg-open.sh"
local XDG_OPEN_REG="Software\Classes\xdg-open\shell\open\command" local XDG_OPEN_REG="Software\Classes\xdg-open\shell\open\command"
if [[ $WH_XDG_OPEN != "0" ]] ; then if [[ $WH_XDG_OPEN != "0" ]] ; then
@@ -1206,13 +1216,25 @@ init_wineprefix () {
# добавляем новую команду xdg-open в реестр # добавляем новую команду xdg-open в реестр
get_and_set_reg_file --add "$XDG_OPEN_REG" '@=' 'REG_SZ' "$WRAPPER %1" "system" get_and_set_reg_file --add "$XDG_OPEN_REG" '@=' 'REG_SZ' "$WRAPPER %1" "system"
# Удаляем старые ассоциации, которых нет в новом списке
if [[ -n "$old_xdg_open" ]]; then
for old_ext in $old_xdg_open; do
if ! echo " $WH_XDG_OPEN " | grep -q " $old_ext "; then
get_and_set_reg_file --delete "Software\Classes\.$old_ext" '@='
fi
done
fi
# добавляем ассоциации файлов для запуска с помощью xdg-open # добавляем ассоциации файлов для запуска с помощью xdg-open
for ext in $WH_XDG_OPEN ; do for ext in $WH_XDG_OPEN ; do
get_and_set_reg_file --add "Software\Classes\.$ext" '@=' 'REG_SZ' "xdg-open" "system" get_and_set_reg_file --add "Software\Classes\.$ext" '@=' 'REG_SZ' "xdg-open" "system"
done done
print_info "Используются ассоциации с нативными приложениями для файлов: \"$WH_XDG_OPEN\"" print_info "Используются ассоциации с нативными приложениями для файлов: \"$WH_XDG_OPEN\""
else else
# удаление команды xdg-open из реестра # удаление всех ассоциаций
for old_ext in $old_xdg_open; do
get_and_set_reg_file --delete "Software\Classes\.$old_ext" '@='
done
get_and_set_reg_file --delete "$XDG_OPEN_REG" '@=' get_and_set_reg_file --delete "$XDG_OPEN_REG" '@='
# удаяем скрипт-обёртку # удаяем скрипт-обёртку
try_remove_file "$WRAPPER" try_remove_file "$WRAPPER"
@@ -1353,6 +1375,13 @@ init_database () {
. "$WHDB_FILE" . "$WHDB_FILE"
elif check_prefix_var && [[ -f "$WINEPREFIX/last.conf" ]] ; then elif check_prefix_var && [[ -f "$WINEPREFIX/last.conf" ]] ; then
print_info "Найдены настройки из предыдущего использования префикса: $WINEPREFIX" print_info "Найдены настройки из предыдущего использования префикса: $WINEPREFIX"
# Сохраняем значение WH_XDG_OPEN, если оно было установлено извне (например, из GUI).
# Это предотвращает его перезапись старым значением из last.conf.
# Используем `declare -p` для надежного определения, была ли переменная установлена,
# даже если она пустая (что означает "удалить все ассоциации").
if declare -p WH_XDG_OPEN &>/dev/null; then
wh_xdg_open_from_env="$WH_XDG_OPEN"
fi
cat "$WINEPREFIX/last.conf" cat "$WINEPREFIX/last.conf"
. "$WINEPREFIX/last.conf" . "$WINEPREFIX/last.conf"
else else
@@ -1361,10 +1390,19 @@ init_database () {
} }
prepair_wine () { prepair_wine () {
# Объявляем переменную здесь, чтобы она была доступна после вызова init_database
local wh_xdg_open_from_env
if [[ -n "$INSTALL_SCRIPT_NAME" ]] if [[ -n "$INSTALL_SCRIPT_NAME" ]]
then print_info "Используются настройки из скрипта установки: $INSTALL_SCRIPT_NAME" then print_info "Используются настройки из скрипта установки: $INSTALL_SCRIPT_NAME"
else init_database else init_database
fi fi
# Восстанавливаем значение WH_XDG_OPEN, если оно было установлено извне.
# Проверяем, была ли переменная сохранена, а не ее значение.
# Это позволяет корректно передать пустую строку.
if declare -p wh_xdg_open_from_env &>/dev/null; then
export WH_XDG_OPEN="$wh_xdg_open_from_env"
fi
init_wine_ver init_wine_ver
init_wineprefix init_wineprefix
use_winetricks use_winetricks
@@ -1394,9 +1432,9 @@ wine_run () {
echo "##### Лог WINE #####" | tee -a "$LOG_FILE" echo "##### Лог WINE #####" | tee -a "$LOG_FILE"
$MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS 2>&1 | tee -a "$LOG_FILE" $MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS 2>&1 | tee -a "$LOG_FILE"
else else
$MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS exec $MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS
fi fi
wait_wineserver # wait_wineserver
} }
wine_run_install () { wine_run_install () {

View File

@@ -12,7 +12,7 @@ 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) QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox)
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, QDesktopServices
from PyQt5.QtNetwork import QLocalServer, QLocalSocket from PyQt5.QtNetwork import QLocalServer, QLocalSocket
@@ -474,10 +474,9 @@ class WinetricksManagerDialog(QDialog):
self.log_output.setText(self.INFO_TEXT) self.log_output.setText(self.INFO_TEXT)
main_layout.addWidget(self.log_output) main_layout.addWidget(self.log_output)
# Кнопки управления # Кнопки управления, выровненные по правому краю
button_layout = QHBoxLayout() button_layout = QHBoxLayout()
self.status_label = QLabel("Загрузка компонентов...") button_layout.addStretch(1)
button_layout.addWidget(self.status_label, 1)
self.apply_button = QPushButton("Применить") self.apply_button = QPushButton("Применить")
self.apply_button.setEnabled(False) self.apply_button.setEnabled(False)
@@ -548,7 +547,6 @@ class WinetricksManagerDialog(QDialog):
def load_all_categories(self): def load_all_categories(self):
"""Запускает загрузку всех категорий.""" """Запускает загрузку всех категорий."""
self.loading_count = len(self.categories) self.loading_count = len(self.categories)
self.category_statuses = {name: "загрузка..." for name in self.categories.keys()}
for internal_name in self.categories.values(): for internal_name in self.categories.values():
self._start_load_process(internal_name) self._start_load_process(internal_name)
@@ -602,13 +600,6 @@ class WinetricksManagerDialog(QDialog):
process.finished.connect(partial(self._on_load_finished, category)) process.finished.connect(partial(self._on_load_finished, category))
process.start(self.winetricks_path, [category, "list"]) 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): def _parse_winetricks_log(self):
"""Читает winetricks.log и возвращает множество установленных компонентов.""" """Читает winetricks.log и возвращает множество установленных компонентов."""
installed_verbs = set() installed_verbs = set()
@@ -681,22 +672,15 @@ class WinetricksManagerDialog(QDialog):
if exit_code != 0 or exit_status != QProcess.NormalExit: if exit_code != 0 or exit_status != QProcess.NormalExit:
error_string = process.errorString() if process else "N/A" error_string = process.errorString() if process else "N/A"
self._log(f"--- Ошибка загрузки категории '{category}' (код: {exit_code}) ---", "red") self._log(f"--- Ошибка загрузки категории '{category_display_name}' (код: {exit_code}) ---", "red")
self.category_statuses[category_display_name] = "ошибка"
self._update_status_label() # Показываем ошибку в статусе
if exit_status == QProcess.CrashExit: if exit_status == QProcess.CrashExit:
self._log("--- Процесс winetricks завершился аварийно. ---", "red") self._log("--- Процесс winetricks завершился аварийно. ---", "red")
# По умолчанию используется "Неизвестная ошибка", которая не очень полезна.
if error_string != "Неизвестная ошибка": if error_string != "Неизвестная ошибка":
self._log(f"--- Системная ошибка: {error_string} ---", "red") self._log(f"--- Системная ошибка: {error_string} ---", "red")
self._log(output if output.strip() else "Winetricks не вернул вывод. Проверьте, что он работает корректно.") self._log(output if output.strip() else "Winetricks не вернул вывод. Проверьте, что он работает корректно.")
self._log("--------------------------------------------------", "red") self._log("--------------------------------------------------", "red")
else: else:
self.category_statuses[category_display_name] = "готово"
installed_verbs = self._parse_winetricks_log() 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) found_items = self._parse_winetricks_list_output(output, installed_verbs, list_widget)
if from_cache is None: # Только если мы не читали из кэша if from_cache is None: # Только если мы не читали из кэша
@@ -721,7 +705,6 @@ class WinetricksManagerDialog(QDialog):
self.loading_count -= 1 self.loading_count -= 1
if self.loading_count == 0: if self.loading_count == 0:
self.status_label.setText("Готово.")
self._update_ui_state() self._update_ui_state()
def _on_item_changed(self, item): def _on_item_changed(self, item):
@@ -862,11 +845,6 @@ class WinetricksManagerDialog(QDialog):
# 3. Обрабатываем успех # 3. Обрабатываем успех
self._log("\n=== Все операции успешно завершены ===") self._log("\n=== Все операции успешно завершены ===")
self._show_message_box("Успех",
"Операции с компонентами были успешно выполнены.",
QMessageBox.Information,
{"buttons": {"Да": QMessageBox.AcceptRole}})
self.apply_button.setEnabled(True) self.apply_button.setEnabled(True)
self.reinstall_button.setEnabled(False) # Сбрасываем в неактивное состояние self.reinstall_button.setEnabled(False) # Сбрасываем в неактивное состояние
self.close_button.setEnabled(True) self.close_button.setEnabled(True)
@@ -876,7 +854,6 @@ class WinetricksManagerDialog(QDialog):
search_edit.clear() search_edit.clear()
# Перезагружаем данные, чтобы обновить состояние # Перезагружаем данные, чтобы обновить состояние
self.status_label.setText("Обновление данных...")
self.initial_states.clear() self.initial_states.clear()
self.load_all_categories() self.load_all_categories()
self.installation_finished = True self.installation_finished = True
@@ -1355,6 +1332,75 @@ class CreatePrefixDialog(QDialog):
self.accept() 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): class ComponentVersionSelectionDialog(QDialog):
"""Диалог для выбора версии компонента (DXVK, VKD3D).""" """Диалог для выбора версии компонента (DXVK, VKD3D)."""
@@ -2142,6 +2188,13 @@ class WineHelperGUI(QMainWindow):
self.vkd3d_manage_button.setToolTip("Установка или удаление определенной версии vkd3d-proton в префиксе.") self.vkd3d_manage_button.setToolTip("Установка или удаление определенной версии vkd3d-proton в префиксе.")
management_layout.addWidget(self.vkd3d_manage_button, 5, 1) 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_widget = QWidget()
right_column_layout = QVBoxLayout(right_column_widget) right_column_layout = QVBoxLayout(right_column_widget)
@@ -2174,7 +2227,7 @@ class WineHelperGUI(QMainWindow):
right_column_layout.setStretch(0, 1) # Информационное окно растягивается right_column_layout.setStretch(0, 1) # Информационное окно растягивается
right_column_layout.setStretch(1, 0) # Группа кнопок не растягивается right_column_layout.setStretch(1, 0) # Группа кнопок не растягивается
management_layout.addWidget(right_column_widget, 0, 2, 6, 1) management_layout.addWidget(right_column_widget, 0, 2, 7, 1)
management_layout.setColumnStretch(0, 1) management_layout.setColumnStretch(0, 1)
management_layout.setColumnStretch(1, 1) management_layout.setColumnStretch(1, 1)
@@ -2381,8 +2434,9 @@ class WineHelperGUI(QMainWindow):
"VKD3D_VER": ("Версия VKD3D", lambda v: v if v else "Не установлено"), "VKD3D_VER": ("Версия VKD3D", lambda v: v if v else "Не установлено"),
"WINEESYNC": ("ESync", lambda v: "Включен" if v == "1" else "Выключен"), "WINEESYNC": ("ESync", lambda v: "Включен" if v == "1" else "Выключен"),
"WINEFSYNC": ("FSync", 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'<p style="line-height: 1.3; font-size: 9pt;">'
html_content += f"<b>Имя:</b> {html.escape(prefix_name)}<br>" html_content += f"<b>Имя:</b> {html.escape(prefix_name)}<br>"
@@ -2682,6 +2736,64 @@ class WineHelperGUI(QMainWindow):
if exit_code == 0: if exit_code == 0:
self.update_prefix_info_display(prefix_name) 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): def create_launcher_for_prefix(self):
""" """
Открывает диалог для создания ярлыка для приложения внутри выбранного префикса. Открывает диалог для создания ярлыка для приложения внутри выбранного префикса.
@@ -3514,7 +3626,7 @@ class WineHelperGUI(QMainWindow):
QMessageBox.critical(self, "Ошибка", f"Не удалось модифицировать команду для отладки: {e}") QMessageBox.critical(self, "Ошибка", f"Не удалось модифицировать команду для отладки: {e}")
return return
process = QProcess(self) process = QProcess()
env = QProcessEnvironment.systemEnvironment() env = QProcessEnvironment.systemEnvironment()
cmd_start_index = 0 cmd_start_index = 0
@@ -3532,7 +3644,10 @@ class WineHelperGUI(QMainWindow):
arguments = clean_command[cmd_start_index + 1:] arguments = clean_command[cmd_start_index + 1:]
process.setProcessEnvironment(env) 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: try:
process.start(program, arguments) process.start(program, arguments)
@@ -3551,6 +3666,36 @@ 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 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): def uninstall_app(self):
"""Удаляет выбранное установленное приложение и его префикс""" """Удаляет выбранное установленное приложение и его префикс"""
if not self.current_selected_app or 'desktop_path' not in self.current_selected_app: if not self.current_selected_app or 'desktop_path' not in self.current_selected_app: