Compare commits

..

13 Commits

Author SHA1 Message Date
Sergey Palcheh
9e8e41e812 the license agreement window has been removed when creating a prefix 2025-10-07 15:03:08 +06:00
Sergey Palcheh
5f4d3a54b1 fixed auto-scrolling of the log to the final message when creating a prefix 2025-10-07 14:55:37 +06:00
Sergey Palcheh
9d16883e6e added display of installed Winetricks components 2025-10-07 14:28:18 +06:00
Sergey Palcheh
e3cafee4f5 added a filter for unnecessary components 2025-10-07 13:35:29 +06:00
Sergey Palcheh
7113d864be added the Open Prefix folder button in the file manager 2025-10-03 14:23:48 +06:00
Sergey Palcheh
ad775e4644 added the Delete all WineHelper data button 2025-10-02 15:45:24 +06:00
Sergey Palcheh
36f5c306c5 remove-all: added removal of /home/minergenon/winehelper_backup_log 2025-10-02 14:51:37 +06:00
Sergey Palcheh
173a7ae41f README: added a description of creating a log and backup 2025-10-02 14:38:58 +06:00
Sergey Palcheh
32e56879f7 fixed the appearance of the Open folder with the prefix's log/backup button 2025-10-02 13:54:49 +06:00
Sergey Palcheh
b88ea9f478 the Open folder with the log button has been renamed to Open folder with the log/backup prefix 2025-10-02 13:41:59 +06:00
Sergey Palcheh
dd82cee5ea the backup copy of the prefix has been moved to the home directory 2025-10-02 13:22:54 +06:00
Sergey Palcheh
a35efc347d added a button to open the location of the log file 2025-10-02 12:59:07 +06:00
Sergey Palcheh
95a8d370ab winehelper.log moved to the winehelper_backup_log directory 2025-10-02 12:24:21 +06:00
4 changed files with 212 additions and 15 deletions

View File

@@ -298,6 +298,13 @@ WineHelper предоставляет доступ к основным инст
<p><em>Выбранное приложение во вкладке "Установленные"</em></p>
</div>
Расположения лога запуска программы а также резервной копии префикса можно просмотреть с помощью кнопки **«Открыть папку с логом/резервной копией префикса»** которая появляется автоматически после создания лога или резервной копии.
<div align="center">
<img src="image/handbook/folder_log_backup.png">
<p><em>Кнопка "Открыть папку с логом/резервной копией префикса"</em></p>
</div>
### Вкладка «Менеджер префиксов»
Эта вкладка предоставляет мощные инструменты для управления префиксами Wine.

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@@ -102,7 +102,9 @@ else
fi
if [[ $WINEDEBUG != "-all" ]] ; then
export LOG_FILE="$HOME/winehelper.log"
log_dir="$HOME/winehelper_backup_log"
mkdir -p "$log_dir"
export LOG_FILE="$log_dir/winehelper.log"
date > "$LOG_FILE"
print_warning "Включен режим логирования работы WINE."
print_warning "Лог будет сохранен по пути: $LOG_FILE"
@@ -1773,6 +1775,7 @@ remove_winehelper () {
echo " - Все настройки WineHelper"
echo " - Все приложения/программы, установленные через WineHelper"
echo " - Все ярлыки из меню и с рабочего стола, созданные с помощью WineHelper"
echo " - Все резервные копии и логи, созданные WineHelper"
echo "======================================================"
if print_confirmation "Продолжить?" ; then
echo "----------------------------------------------"
@@ -1788,6 +1791,7 @@ remove_winehelper () {
# Удаление рабочих каталогов
try_remove_dir "$USER_WORK_PATH"
try_remove_dir "$HOME/winehelper_backup_log"
# Удаление файлов меню
try_remove_dir "$WH_MENU_DIR"
@@ -1866,7 +1870,8 @@ backup_prefix() {
check_prefix_var
local backup_base_dir backup_archive_name backup_dest_path temp_backup_dir temp_prefix_dir temp_users_dir
backup_base_dir="$(xdg-user-dir DESKTOP)"
backup_base_dir="$HOME/winehelper_backup_log"
create_new_dir "$backup_base_dir"
backup_archive_name="backup_${PREFIX_NAME}_$(date +%d.%m.%Y-%H.%M.%S).whpack"
backup_dest_path="$backup_base_dir/$backup_archive_name"
temp_backup_dir="$WH_TMP_DIR/backup_${PREFIX_NAME}_$(date +%d.%m.%Y-%H.%M.%S)"

View File

@@ -13,7 +13,7 @@ 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, 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, pyqtSignal
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
@@ -428,6 +428,8 @@ class WinetricksManagerDialog(QDialog):
"Для переустановки компонента: Выделите его в списке и нажмите кнопку «Переустановить»."
)
installation_complete = pyqtSignal()
def __init__(self, prefix_path, winetricks_path, parent=None, wine_executable=None):
super().__init__(parent)
self.prefix_path = prefix_path
@@ -617,12 +619,33 @@ class WinetricksManagerDialog(QDialog):
self._log(f"--- Предупреждение: не удалось прочитать {log_path}: {e} ---")
return installed_verbs
def _parse_winetricks_list_output(self, output, installed_verbs, list_widget):
def _parse_winetricks_list_output(self, output, installed_verbs, list_widget, category):
"""Парсит вывод 'winetricks list' и заполняет QListWidget."""
# Regex, который обрабатывает строки как с префиксом статуса '[ ]', так и без него.
# 1. `(?:\[(.)]\s+)?` - опциональная группа для статуса (напр. '[x]').
# 2. `([^\s]+)` - имя компонента (без пробелов).
# 3. `(.*)` - оставшаяся часть строки (описание).
# Определяем шаблоны для фильтрации на основе категории
dlls_blacklist_pattern = None
fonts_blacklist_pattern = None
settings_blacklist_pattern = None
if category == 'dlls':
# Исключаем d3d*, directx9, dont_use, dxvk*, vkd3d*, galliumnine, faudio*, Foundation
dlls_blacklist_pattern = re.compile(
r'^(d3d|directx9|dont_use|dxvk|vkd3d|galliumnine|faudio|foundation)', re.IGNORECASE
)
elif category == 'fonts':
fonts_blacklist_pattern = re.compile(
r'^(dont_use)', re.IGNORECASE
)
elif category == 'settings':
# Исключаем vista*, alldlls, autostart_*, bad*, good*, win*, videomemory*, vd=*, isolate_home
settings_blacklist_pattern = re.compile(
r'^(vista|alldlls|autostart_|bad|good|win|videomemory|vd=|isolate_home)', re.IGNORECASE
)
line_re = re.compile(r"^\s*(?:\[(.)]\s+)?([^\s]+)\s*(.*)")
found_items = False
@@ -643,6 +666,14 @@ class WinetricksManagerDialog(QDialog):
if '/' in name or '\\' in name or name.lower() in ('executing', 'using', 'warning:') or name.endswith(':'):
continue
# Применяем фильтры для черных списков
if dlls_blacklist_pattern and dlls_blacklist_pattern.search(name):
continue
if fonts_blacklist_pattern and fonts_blacklist_pattern.search(name):
continue
if settings_blacklist_pattern and settings_blacklist_pattern.search(name):
continue
is_checked = name in installed_verbs
item_text = f"{name.ljust(27)}{description.strip()}"
item = QListWidgetItem(item_text)
@@ -681,7 +712,7 @@ class WinetricksManagerDialog(QDialog):
self._log("--------------------------------------------------", "red")
else:
installed_verbs = self._parse_winetricks_log()
found_items = self._parse_winetricks_list_output(output, installed_verbs, list_widget)
found_items = self._parse_winetricks_list_output(output, installed_verbs, list_widget, category)
if from_cache is None: # Только если мы не читали из кэша
# Сохраняем успешный результат в кэш
@@ -856,6 +887,7 @@ class WinetricksManagerDialog(QDialog):
# Перезагружаем данные, чтобы обновить состояние
self.initial_states.clear()
self.load_all_categories()
self.installation_complete.emit()
self.installation_finished = True
def closeEvent(self, event):
@@ -1762,6 +1794,7 @@ class WineHelperGUI(QMainWindow):
if show_global:
self.backup_button.setVisible(False)
self.create_log_button.setVisible(False)
self.open_log_dir_button.setVisible(False)
self.uninstall_button.setVisible(False)
self.current_selected_app = None
@@ -1877,6 +1910,12 @@ class WineHelperGUI(QMainWindow):
self.backup_button.clicked.connect(self.backup_prefix_for_app)
installed_global_layout.addWidget(self.backup_button)
self.open_log_dir_button = QPushButton("Открыть папку с логом/резервной копией префикса")
self.open_log_dir_button.setIcon(QIcon.fromTheme("folder-open"))
self.open_log_dir_button.clicked.connect(self.open_log_directory)
self.open_log_dir_button.setVisible(False) # Скрыта по умолчанию
installed_global_layout.addWidget(self.open_log_dir_button)
self.uninstall_button = QPushButton("Удалить префикс")
self.uninstall_button.setIcon(QIcon.fromTheme("user-trash"))
self.uninstall_button.clicked.connect(self.uninstall_app)
@@ -2172,6 +2211,13 @@ class WineHelperGUI(QMainWindow):
self.created_prefix_selector.currentIndexChanged.connect(self.on_created_prefix_selected)
selector_layout.addWidget(self.created_prefix_selector, 1)
self.open_prefix_folder_button = QPushButton()
self.open_prefix_folder_button.setIcon(QIcon.fromTheme("folder-open"))
self.open_prefix_folder_button.setToolTip("Открыть папку префикса в файловом менеджере")
self.open_prefix_folder_button.setEnabled(False)
self.open_prefix_folder_button.clicked.connect(self.open_selected_prefix_folder)
selector_layout.addWidget(self.open_prefix_folder_button)
self.create_base_pfx_button = QPushButton()
self.create_base_pfx_button.setIcon(QIcon.fromTheme("document-export"))
self.create_base_pfx_button.setToolTip("Создать шаблон из выбранного префикса (для опытных пользователей)")
@@ -2316,6 +2362,24 @@ class WineHelperGUI(QMainWindow):
management_layout.setColumnStretch(2, 2)
container_layout.addWidget(self.prefix_management_groupbox)
# --- Кнопка полного удаления ---
# Добавляем разделитель и кнопку в основной контейнер управления
container_layout.addSpacing(15)
self.remove_all_button = QPushButton("Удалить все данные WineHelper")
self.remove_all_button.setStyleSheet("""
QPushButton:!disabled {
background-color: #d32f2f;
color: white;
padding: 5px;
}
""")
self.remove_all_button.setMinimumHeight(32)
self.remove_all_button.setFont(QFont('Arial', 10, QFont.Bold))
self.remove_all_button.setToolTip("ВНИМАНИЕ: Удаляет ВСЕ данные WineHelper, включая все префиксы, настройки и ярлыки.")
self.remove_all_button.clicked.connect(self.remove_all_data)
container_layout.addWidget(self.remove_all_button)
layout.addWidget(self.management_container_groupbox)
layout.addStretch()
self.add_tab(self.prefix_tab, "Менеджер префиксов")
@@ -2357,6 +2421,7 @@ class WineHelperGUI(QMainWindow):
prefix_names = []
self.created_prefix_selector.blockSignals(True)
self.remove_all_button.setEnabled(bool(prefix_names))
self.created_prefix_selector.clear()
if prefix_names:
self.created_prefix_selector.addItems(prefix_names)
@@ -2372,7 +2437,9 @@ class WineHelperGUI(QMainWindow):
self.current_managed_prefix_name = None
self._setup_prefix_management_panel(None)
self.delete_prefix_button.setEnabled(False)
self.remove_all_button.setEnabled(False)
self.create_base_pfx_button.setEnabled(False)
self.open_prefix_folder_button.setEnabled(False)
else:
# Прокручиваем к выбранному элементу, чтобы он был виден в списке
self.created_prefix_selector.view().scrollTo(
@@ -2382,7 +2449,9 @@ class WineHelperGUI(QMainWindow):
self.current_managed_prefix_name = prefix_name
self._setup_prefix_management_panel(prefix_name)
self.delete_prefix_button.setEnabled(True)
self.remove_all_button.setEnabled(True)
self.create_base_pfx_button.setEnabled(True)
self.open_prefix_folder_button.setEnabled(True)
def delete_selected_prefix(self):
"""Удаляет префикс, выбранный в выпадающем списке на вкладке 'Менеджер префиксов'."""
@@ -2491,6 +2560,21 @@ class WineHelperGUI(QMainWindow):
self._run_simple_command("create-base-pfx", [prefix_name])
self.command_dialog.exec_()
def open_selected_prefix_folder(self):
"""Открывает папку выбранного префикса в системном файловом менеджере."""
prefix_name = self.current_managed_prefix_name
if not prefix_name:
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
if os.path.isdir(prefix_path):
try:
subprocess.Popen(['xdg-open', prefix_path])
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось открыть директорию:\n{prefix_path}\n\nОшибка: {e}")
else:
QMessageBox.warning(self, "Ошибка", f"Директория префикса не найдена:\n{prefix_path}")
def _setup_prefix_management_panel(self, prefix_name):
"""Настраивает панель управления префиксом на основе текущего состояния."""
is_prefix_selected = bool(prefix_name)
@@ -2549,19 +2633,39 @@ class WineHelperGUI(QMainWindow):
self.esync_button.blockSignals(False)
self.fsync_button.blockSignals(False)
# --- Чтение и отображение установленных компонентов Winetricks ---
winetricks_log_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "winetricks.log")
installed_verbs = []
if os.path.exists(winetricks_log_path):
try:
with open(winetricks_log_path, 'r', encoding='utf-8') as f:
for line in f:
verb = line.split('#', 1)[0].strip()
if verb:
installed_verbs.append(verb)
except IOError as e:
print(f"Ошибка чтения winetricks.log: {e}")
# Фильтруем служебные компоненты, чтобы не засорять вывод
verbs_to_ignore = {
'isolate_home', 'winxp', 'win7', 'win10', 'win11',
'vista', 'win2k', 'win2k3', 'win2k8', 'win8', 'win81',
'workaround', 'internal'
}
display_verbs = sorted([v for v in installed_verbs if v not in verbs_to_ignore])
# Карта для красивого отображения известных переменных
display_map = {
"WINEPREFIX": ("Путь", lambda v: v),
"WINEARCH": ("Архитектура", lambda v: "64-bit" if v == "win64" else "32-bit"),
"WH_WINE_USE": ("Версия Wine", lambda v: "Системная" if v == "system" else v),
"BASE_PFX": ("Тип", lambda v: 'Чистый' if v == "none" else 'С рекомендуемыми библиотеками'),
"DXVK_VER": ("Версия DXVK", lambda v: v if v else "Не установлено"),
"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", "WH_XDG_OPEN"]
display_order = ["WINEPREFIX", "WINEARCH", "WH_WINE_USE", "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>"
@@ -2583,6 +2687,15 @@ class WineHelperGUI(QMainWindow):
html_content += "<br><b>Дополнительные параметры:</b><br>"
html_content += other_vars_html
html_content += "<br><b>Компоненты (Winetricks):</b> "
if display_verbs:
# Используем span вместо div, чтобы избежать лишних отступов
html_content += '<span style="max-height: 120px; overflow-y: auto;">'
html_content += ", ".join(html.escape(v) for v in display_verbs)
html_content += '</span>'
else:
html_content += "Не установлены"
html_content += "</p>"
self.prefix_info_display.setHtml(html_content)
@@ -3139,9 +3252,6 @@ class WineHelperGUI(QMainWindow):
"""Открывает диалог создания нового префикса."""
dialog = CreatePrefixDialog(self)
if dialog.exec_() == QDialog.Accepted:
if not self._show_license_agreement_dialog():
return
self.start_prefix_creation(
prefix_name=dialog.prefix_name,
wine_arch=dialog.wine_arch,
@@ -3378,6 +3488,7 @@ class WineHelperGUI(QMainWindow):
self.installed_global_action_widget.setVisible(True)
self.backup_button.setVisible(True)
self.create_log_button.setVisible(True)
self.update_open_log_dir_button_visibility()
self.uninstall_button.setVisible(True)
self.manual_install_path_widget.setVisible(False)
@@ -3386,6 +3497,27 @@ class WineHelperGUI(QMainWindow):
self.current_selected_app = None
self.info_panel.setVisible(False)
def update_open_log_dir_button_visibility(self):
"""Проверяет наличие лог-файла или бэкапов и обновляет видимость кнопки."""
log_dir_path = os.path.join(os.path.expanduser("~"), "winehelper_backup_log")
is_visible = False
if os.path.isdir(log_dir_path):
# Кнопка должна быть видна, если директория не пуста.
if os.listdir(log_dir_path):
is_visible = True
self.open_log_dir_button.setVisible(is_visible)
def open_log_directory(self):
"""Открывает директорию с лог-файлами."""
log_dir_path = os.path.join(os.path.expanduser("~"), "winehelper_backup_log")
if os.path.isdir(log_dir_path):
try:
subprocess.Popen(['xdg-open', log_dir_path])
except Exception as e:
QMessageBox.warning(self, "Ошибка", f"Не удалось открыть директорию:\n{log_dir_path}\n\nОшибка: {e}")
else:
QMessageBox.information(self, "Информация", f"Директория с логами не найдена:\n{log_dir_path}")
def _get_prefix_name_for_selected_app(self):
"""Извлекает имя префикса для выбранного приложения."""
if not self.current_selected_app or 'desktop_path' not in self.current_selected_app:
@@ -3426,8 +3558,8 @@ class WineHelperGUI(QMainWindow):
msg_box = QMessageBox(self)
msg_box.setWindowTitle("Создание резервной копии")
msg_box.setText(
f"Будет создана резервная копия префикса '{prefix_name}'.\n"
f"Файл будет сохранен на вашем Рабочем столе в формате .whpack.\n\nПродолжить?"
f"Будет создана резервная копия префикса '{prefix_name}'.\n\n"
f"Файл будет сохранен в домашней директории в папке winehelper_backup_log/ в формате .whpack.\n\nПродолжить?"
)
msg_box.addButton(yes_button, QMessageBox.YesRole)
msg_box.addButton(no_button, QMessageBox.NoRole)
@@ -3460,6 +3592,7 @@ class WineHelperGUI(QMainWindow):
self.command_process.setProcessChannelMode(QProcess.MergedChannels)
self.command_process.readyReadStandardOutput.connect(self._handle_command_output)
self.command_process.finished.connect(self._handle_command_finished)
self.command_process.finished.connect(self.update_open_log_dir_button_visibility)
winehelper_path = self.winehelper_path
args = ["backup-prefix", prefix_name]
@@ -3525,9 +3658,9 @@ class WineHelperGUI(QMainWindow):
msg_box = QMessageBox(self)
msg_box.setWindowTitle("Создание лога")
msg_box.setText(
"Приложение будет запущено в режиме отладки.\n"
"После закрытия приложения лог будет сохранен в вашем домашнем каталоге "
"под именем 'winehelper.log'."
"Приложение будет запущено в режиме отладки.\n\n"
"После закрытия приложения лог будет сохранен в папке 'winehelper_backup_log' "
"в вашем домашнем каталоге под именем 'winehelper.log'."
)
msg_box.addButton(yes_button, QMessageBox.YesRole)
msg_box.addButton(no_button, QMessageBox.NoRole)
@@ -3569,6 +3702,7 @@ class WineHelperGUI(QMainWindow):
wine_executable = self._get_wine_executable_for_prefix(prefix_name)
dialog = WinetricksManagerDialog(prefix_path, winetricks_path, self, wine_executable=wine_executable)
dialog.installation_complete.connect(lambda: self.update_prefix_info_display(prefix_name))
dialog.exec_()
def _get_wine_executable_for_prefix(self, prefix_name):
@@ -3793,6 +3927,7 @@ class WineHelperGUI(QMainWindow):
# и избегания проблем с замыканием в lambda.
process.finished.connect(partial(self._on_app_process_finished, desktop_path))
process.finished.connect(self.update_open_log_dir_button_visibility)
try:
process.start(program, arguments)
@@ -3811,6 +3946,55 @@ class WineHelperGUI(QMainWindow):
QMessageBox.critical(self, "Ошибка",
f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}")
def remove_all_data(self):
"""Запускает процесс полного удаления всех данных WineHelper."""
# Первое подтверждение
msg_box1 = QMessageBox(self)
msg_box1.setIcon(QMessageBox.Critical)
msg_box1.setWindowTitle('Подтверждение полного удаления')
msg_box1.setText(
"<h2>ВНИМАНИЕ!</h2>"
"<p>Это действие полностью и безвозвратно удалит <b>ВСЕ</b> данные, связанные с WineHelper, включая:</p>"
"<ul>"
"<li>Все созданные префиксы и установленные в них программы.</li>"
"<li>Все ярлыки в меню и на рабочем столе.</li>"
"<li>Все настройки, кэш и резервные копии.</li>"
"</ul>"
"<p>Продолжить?</p>"
)
msg_box1.setTextFormat(Qt.RichText)
yes_button1 = msg_box1.addButton("Да, я понимаю", QMessageBox.YesRole)
no_button1 = msg_box1.addButton("Отмена", QMessageBox.NoRole)
msg_box1.setDefaultButton(no_button1)
msg_box1.exec_()
if msg_box1.clickedButton() != yes_button1:
return
# Второе, финальное подтверждение
msg_box2 = QMessageBox(self)
msg_box2.setIcon(QMessageBox.Critical)
msg_box2.setWindowTitle('Последнее предупреждение')
msg_box2.setText("<h3>Вы уверены, что хотите удалить ВСЁ?</h3><p>Это действие необратимо.</p>")
msg_box2.setTextFormat(Qt.RichText)
yes_button2 = msg_box2.addButton("Да, удалить всё", QMessageBox.DestructiveRole)
no_button2 = msg_box2.addButton("Нет, я передумал", QMessageBox.RejectRole)
msg_box2.setDefaultButton(no_button2)
msg_box2.exec_()
if msg_box2.clickedButton() != yes_button2:
return
# Запускаем команду и выходим из приложения
try:
# Запускаем команду в фоне и не ждем ее завершения
subprocess.Popen([self.winehelper_path, "remove-all", "--force"])
# Сообщаем пользователю и закрываем GUI
QMessageBox.information(self, "Удаление", "Запущена процедура удаления WineHelper. Приложение будет закрыто.")
self.quit_application()
except Exception as e:
QMessageBox.critical(self, "Ошибка", f"Не удалось запустить команду удаления: {e}")
def quit_application(self):
"""Инициирует процесс выхода из приложения."""
self.is_quitting = True
@@ -4511,6 +4695,7 @@ class WineHelperGUI(QMainWindow):
self.command_process.deleteLater()
self.command_process = None
self.command_close_button.setEnabled(True)
self.command_log_output.ensureCursorVisible()
def _handle_launcher_creation_finished(self, exit_code, exit_status):
"""Обрабатывает завершение создания ярлыка."""