diff --git a/winehelper_gui.py b/winehelper_gui.py index 0fac558..93c9466 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -2743,36 +2743,18 @@ class WineHelperGUI(QMainWindow): QMessageBox.warning(self, "Ошибка", "Указан неверный путь к установочному файлу.") return - self.command_dialog = QDialog(self) - self.command_dialog.setWindowTitle(f"Установка в префикс: {prefix_name}") - 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(self._handle_prefix_install_finished) - - # Окружение полностью настраивается скриптом winehelper - self.command_process.setProcessEnvironment(QProcessEnvironment.systemEnvironment()) - - 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_() + dialog, process, log_output, close_button = self._create_command_dialog( + f"Установка в префикс: {prefix_name}", + self.winehelper_path, + ["install-to-prefix", prefix_name, installer_path], + finished_callback=self._handle_prefix_install_finished + ) + # Сохраняем ссылки для обратной совместимости с существующим кодом + self.command_dialog = dialog + self.command_process = process + self.command_log_output = log_output + self.command_close_button = close_button + dialog.exec_() def _get_prefix_component_version(self, prefix_name, component_key): """ @@ -3035,41 +3017,22 @@ class WineHelperGUI(QMainWindow): 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) + def on_finished(exit_code, exit_status): + self._handle_component_install_finished(prefix_name, exit_code, exit_status) - 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) - # Переменная WH_XDG_OPEN теперь читается из измененного last.conf - self.command_process.setProcessEnvironment(env) - - # Вызываем init-prefix, который теперь прочитает правильное значение из last.conf - 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_() + dialog, process, log_output, close_button = self._create_command_dialog( + "Обновление ассоциаций файлов", + self.winehelper_path, + ["init-prefix"], + env_vars={"WINEPREFIX": prefix_path}, + finished_callback=on_finished + ) + # Сохраняем ссылки для обратной совместимости с существующим кодом + self.command_dialog = dialog + self.command_process = process + self.command_log_output = log_output + self.command_close_button = close_button + dialog.exec_() def create_launcher_for_prefix(self): """ @@ -3111,38 +3074,19 @@ class WineHelperGUI(QMainWindow): return # Пользователь отменил или ввел пустое имя # 3. Вызываем winehelper.sh create-desktop - self.command_dialog = QDialog(self) - self.command_dialog.setWindowTitle(f"Создание ярлыка для: {app_name}") - 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(self._handle_launcher_creation_finished) - - env = QProcessEnvironment.systemEnvironment() - env.insert("WINEPREFIX", prefix_path) - self.command_process.setProcessEnvironment(env) - - args = ["desktop", app_name, exe_path, "auto"] - - 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_() + dialog, process, log_output, close_button = self._create_command_dialog( + f"Создание ярлыка для: {app_name}", + self.winehelper_path, + ["desktop", app_name, exe_path, "auto"], + env_vars={"WINEPREFIX": prefix_path}, + finished_callback=self._handle_launcher_creation_finished + ) + # Сохраняем ссылки для обратной совместимости с существующим кодом + self.command_dialog = dialog + self.command_process = process + self.command_log_output = log_output + self.command_close_button = close_button + dialog.exec_() def create_help_tab(self): """Создает вкладку 'Справка' с подвкладками""" @@ -3439,16 +3383,26 @@ class WineHelperGUI(QMainWindow): def _handle_prefix_install_finished(self, exit_code, exit_status): """Обрабатывает завершение установки в префикс.""" - if exit_code == 0: - self.command_log_output.append("\n=== Установка успешно завершена ===") - self.create_launcher_button.setEnabled(True) # Активируем кнопку создания ярлыка - else: - self.command_log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===") + # Используем старый API для обратной совместимости + log_output = getattr(self, 'command_log_output', None) + process = getattr(self, 'command_process', None) + close_button = getattr(self, 'command_close_button', None) + + if log_output: + if exit_code == 0: + log_output.append("\n=== Установка успешно завершена ===") + self.create_launcher_button.setEnabled(True) # Активируем кнопку создания ярлыка + else: + log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===") + + if process: + process.deleteLater() + if hasattr(self, 'command_process') and self.command_process == process: + self.command_process = None + + if close_button: + close_button.setEnabled(True) - if self.command_process: - self.command_process.deleteLater() - self.command_process = None - self.command_close_button.setEnabled(True) self.update_installed_apps() def _set_active_button(self, button_widget): @@ -3609,37 +3563,29 @@ class WineHelperGUI(QMainWindow): if msg_box.clickedButton() != yes_button: return # Отмена, если нажато "Нет" или крестик - # Используем модальный диалог для отображения процесса резервного копирования (бэкап) - self.command_dialog = QDialog(self) - self.command_dialog.setWindowTitle(f"Резервное копирование: {prefix_name}") - 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) + dialog, process, log, close_btn = self._create_command_dialog( + f"Резервное копирование: {prefix_name}", + self.winehelper_path, + ["backup-prefix", prefix_name] + ) + # Сохраняем ссылки для обратной совместимости с существующим кодом + self.command_dialog = dialog + self.command_process = process + self.command_log_output = log + self.command_close_button = close_btn - 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(self._handle_command_finished) - self.command_process.finished.connect(self.update_open_log_dir_button_visibility) + # Создаем callback после сохранения ссылок + def on_finished(exit_code, exit_status): + self._handle_command_finished(exit_code, exit_status, close_btn, process, log) + if exit_code == 0: + self.update_open_log_dir_button_visibility() - winehelper_path = self.winehelper_path - args = ["backup-prefix", prefix_name] + # Заменяем стандартный обработчик на наш + process.finished.disconnect() + process.finished.connect(on_finished) - self.command_log_output.append(f"Выполнение: {shlex.quote(winehelper_path)} {' '.join(shlex.quote(a) for a in args)}") - self.command_process.start(winehelper_path, args) - self.command_dialog.exec_() + dialog.exec_() def restore_prefix(self): """Восстанавливает префикс из резервной копии.""" @@ -3653,36 +3599,21 @@ class WineHelperGUI(QMainWindow): if not backup_path: return - # Используем модальный диалог для отображения процесса восстановления из бэкапа - self.command_dialog = QDialog(self) - self.command_dialog.setWindowTitle(f"Восстановление из: {os.path.basename(backup_path)}") - self.command_dialog.setMinimumSize(600, 400) - self.command_dialog.setModal(True) - self.command_dialog.setWindowFlags(self.command_dialog.windowFlags() & ~Qt.WindowCloseButtonHint) + def on_finished(exit_code, exit_status): + self._handle_restore_finished(exit_code, exit_status) - 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(self._handle_restore_finished) - - winehelper_path = self.winehelper_path - args = ["restore-prefix", backup_path] - - self.command_log_output.append(f"Выполнение: {shlex.quote(winehelper_path)} {' '.join(shlex.quote(a) for a in args)}") - self.command_process.start(winehelper_path, args) - self.command_dialog.exec_() + dialog, process, log_output, close_button = self._create_command_dialog( + f"Восстановление из: {os.path.basename(backup_path)}", + self.winehelper_path, + ["restore-prefix", backup_path], + finished_callback=on_finished + ) + # Сохраняем ссылки для обратной совместимости с существующим кодом + self.command_dialog = dialog + self.command_process = process + self.command_log_output = log_output + self.command_close_button = close_button + dialog.exec_() def run_installed_app_with_debug(self): """Запускает выбранное установленное приложение с созданием лога""" @@ -4718,6 +4649,74 @@ class WineHelperGUI(QMainWindow): self.installation_cancelled = True self.install_process.terminate() + def _create_command_dialog(self, title, command, args, env_vars=None, finished_callback=None, + min_width=750, min_height=400): + """ + Создает стандартный модальный диалог для выполнения команд winehelper. + + Args: + title: Заголовок диалога + command: Путь к исполняемому файлу + args: Список аргументов команды + env_vars: Словарь дополнительных переменных окружения (опционально) + finished_callback: Функция обратного вызова при завершении (exit_code, exit_status) + min_width: Минимальная ширина диалога + min_height: Минимальная высота диалога + + Returns: + Кортеж (dialog, process, log_output, close_button) + """ + dialog = QDialog(self) + dialog.setWindowTitle(title) + dialog.setMinimumSize(min_width, min_height) + dialog.setModal(True) + dialog.setWindowFlags(dialog.windowFlags() & ~Qt.WindowCloseButtonHint) + + layout = QVBoxLayout() + log_output = QTextEdit() + log_output.setReadOnly(True) + log_output.setFont(QFont('DejaVu Sans Mono', 10)) + layout.addWidget(log_output) + + close_button = QPushButton("Закрыть") + close_button.setEnabled(False) + close_button.clicked.connect(dialog.close) + layout.addWidget(close_button) + dialog.setLayout(layout) + + process = QProcess(dialog) + process.setProcessChannelMode(QProcess.MergedChannels) + process.readyReadStandardOutput.connect( + lambda: self._append_command_output(process, log_output) + ) + + if finished_callback: + process.finished.connect(finished_callback) + else: + process.finished.connect( + lambda exit_code, exit_status: self._handle_command_finished( + exit_code, exit_status, close_button, process, log_output) + ) + + env = QProcessEnvironment.systemEnvironment() + if env_vars: + for key, value in env_vars.items(): + env.insert(key, value) + process.setProcessEnvironment(env) + + log_output.append(f"Выполнение: {shlex.quote(command)} {' '.join(shlex.quote(a) for a in args)}") + process.start(command, args) + + return dialog, process, log_output, close_button + + def _append_command_output(self, process, log_output): + """Добавляет вывод процесса в лог.""" + output_bytes = process.readAll() + output = output_bytes.data().decode('utf-8', errors='ignore').strip() + if output: + log_output.append(output) + QApplication.processEvents() + def _handle_command_output(self): """Обрабатывает вывод для общих команд в модальном диалоге.""" if hasattr(self, 'command_process') and self.command_process: @@ -4735,20 +4734,34 @@ class WineHelperGUI(QMainWindow): self.command_process.readyReadStandardOutput.connect(self._handle_command_output) self.command_process.finished.connect(self._handle_command_finished) self.command_process.start(self.winehelper_path, [command] + (args or [])) - def _handle_command_finished(self, exit_code, exit_status): + def _handle_command_finished(self, exit_code, exit_status, close_button=None, process=None, log_output=None): """Обрабатывает завершение для диалога команды""" - if exit_code == 0: - self.command_log_output.append(f"\n=== Команда успешно завершена ===") - else: - self.command_log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===") - if self.command_process: - self.command_process.deleteLater() - self.command_process = None - self.command_close_button.setEnabled(True) - self.command_log_output.ensureCursorVisible() + # Поддержка старого API для обратной совместимости + if close_button is None: + close_button = getattr(self, 'command_close_button', None) + if process is None: + process = getattr(self, 'command_process', None) + if log_output is None: + log_output = getattr(self, 'command_log_output', None) + + if log_output: + if exit_code == 0: + log_output.append(f"\n=== Команда успешно завершена ===") + else: + log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===") + log_output.ensureCursorVisible() + + if process: + process.deleteLater() + if hasattr(self, 'command_process') and self.command_process == process: + self.command_process = None + + if close_button: + close_button.setEnabled(True) def _handle_launcher_creation_finished(self, exit_code, exit_status): """Обрабатывает завершение создания ярлыка.""" + # Используем старый API для обратной совместимости self._handle_command_finished(exit_code, exit_status) if exit_code == 0: self.update_installed_apps() @@ -4760,17 +4773,27 @@ class WineHelperGUI(QMainWindow): def _handle_restore_finished(self, exit_code, exit_status): """Обрабатывает завершение для диалога команды восстановления.""" - if exit_code == 0: - self.command_log_output.append(f"\n=== Восстановление успешно завершено ===") - self.update_installed_apps() - self._load_created_prefixes() - self.filter_installed_buttons() - else: - self.command_log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===") - if self.command_process: - self.command_process.deleteLater() - self.command_process = None - self.command_close_button.setEnabled(True) + # Используем старый API для обратной совместимости + log_output = getattr(self, 'command_log_output', None) + if log_output: + if exit_code == 0: + log_output.append(f"\n=== Восстановление успешно завершено ===") + self.update_installed_apps() + self._load_created_prefixes() + self.filter_installed_buttons() + else: + log_output.append(f"\n=== Ошибка выполнения (код: {exit_code}) ===") + + close_button = getattr(self, 'command_close_button', None) + process = getattr(self, 'command_process', None) + + if process: + process.deleteLater() + if hasattr(self, 'command_process') and self.command_process == process: + self.command_process = None + + if close_button: + close_button.setEnabled(True) def cleanup_process(self): """Очищает ресурсы процесса, принудительно завершая его, если он активен."""