diff --git a/winehelper_gui.py b/winehelper_gui.py index f4621ba..d2c6d99 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1275,6 +1275,7 @@ class WineHelperGUI(QMainWindow): self.current_active_button = None self.installed_buttons = [] self.install_tabs_data = {} + self.running_apps = {} # {desktop_path: QProcess} self.current_selected_app = None self.icon_animators = {} self.previous_tab_index = 0 @@ -1483,7 +1484,7 @@ class WineHelperGUI(QMainWindow): # --- Верхний ряд кнопок --- top_buttons_layout = QHBoxLayout() self.run_button = QPushButton("Запустить") - self.run_button.clicked.connect(self.run_installed_app) + self.run_button.clicked.connect(self.toggle_run_stop_app) top_buttons_layout.addWidget(self.run_button) installed_action_layout.addLayout(top_buttons_layout) @@ -2704,6 +2705,12 @@ class WineHelperGUI(QMainWindow): self.current_selected_app['name'] = name self.current_selected_app['exec'] = exec_cmd + # Состояния кнопки + if desktop_path in self.running_apps: + self.run_button.setText("Остановить") + else: + self.run_button.setText("Запустить") + # Показываем панель информации self.info_panel.setVisible(True) @@ -2853,6 +2860,11 @@ class WineHelperGUI(QMainWindow): def run_installed_app_with_debug(self): """Запускает выбранное установленное приложение с созданием лога""" + if self.current_selected_app and self.current_selected_app.get('desktop_path') in self.running_apps: + QMessageBox.information(self, "Приложение запущено", + "Приложение уже запущено. Остановите его, прежде чем запускать с отладкой.") + return + # Создаем кастомные кнопки yes_button = QPushButton("Да") no_button = QPushButton("Нет") @@ -2973,23 +2985,81 @@ class WineHelperGUI(QMainWindow): QMessageBox.critical(self, "Ошибка запуска", f"Не удалось запустить команду:\n{' '.join(command)}\n\nОшибка: {str(e)}") - def run_installed_app(self): - """Запускает выбранное установленное приложение""" - self._run_app_launcher(debug=False) + def toggle_run_stop_app(self): + """Запускает или останавливает выбранное приложение.""" + if not self.current_selected_app or 'desktop_path' not in self.current_selected_app: + QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.") + return + + desktop_path = self.current_selected_app['desktop_path'] + + # Если приложение запущено, останавливаем его + if desktop_path in self.running_apps: + process = self.running_apps.get(desktop_path) + if process and process.state() != QProcess.NotRunning: + prefix_name = self._get_prefix_name_for_selected_app() + if not prefix_name: + QMessageBox.warning(self, "Ошибка", "Не удалось определить префикс для остановки приложения.\n" + "Попробуйте закрыть приложение вручную.") + # Fallback to killing the wrapper script, though it might not work + process.terminate() + if not process.waitForFinished(1000): + process.kill() + return + + prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + wine_executable = self._get_wine_executable_for_prefix(prefix_name) + + wineserver_path = os.path.join(os.path.dirname(wine_executable), "wineserver") + if not shutil.which(wineserver_path): + wineserver_path = "wineserver" + + kill_proc = QProcess(self) + kill_env = QProcessEnvironment.systemEnvironment() + kill_env.insert("WINEPREFIX", prefix_path) + kill_proc.setProcessEnvironment(kill_env) + + print(f"Остановка приложений в префиксе '{prefix_name}'...") + kill_proc.start(wineserver_path, ["-k"]) + kill_proc.waitForFinished(5000) # Даем до 5 секунд на выполнение. + else: + # Состояние не совпадает, убираем из словаря + print(f"Процесс для {desktop_path} уже не запущен, очистка.") + self._on_app_process_finished(desktop_path) + + # Если приложение не запущено, запускаем его + else: + # Запускаем без отладки. Кнопка отладки отдельная. + self._run_app_launcher(debug=False) + + def _on_app_process_finished(self, desktop_path): + """Обрабатывает завершение процесса запущенного приложения.""" + if desktop_path in self.running_apps: + process = self.running_apps.pop(desktop_path) + process.deleteLater() # Clean up the QProcess object + print(f"Процесс для {desktop_path} завершен.") + + # Если текущее выбранное приложение - то, что только что завершилось, обновляем кнопку + if self.current_selected_app and self.current_selected_app.get('desktop_path') == desktop_path: + self.run_button.setText("Запустить") + else: + print(f"Предупреждение: получен сигнал finished для неизвестного процесса {desktop_path}") def _run_app_launcher(self, debug=False): - """Внутренний метод для запуска приложения (с отладкой или без)""" + """Внутренний метод для запуска приложения (с отладкой или без) с использованием QProcess.""" if not self.current_selected_app or 'exec' not in self.current_selected_app: QMessageBox.warning(self, "Ошибка", "Сначала выберите приложение.") return + desktop_path = self.current_selected_app['desktop_path'] command_str = self.current_selected_app['exec'] - try: - # Используем shlex для безопасного разбора командной строки - command_parts = shlex.split(command_str) + if desktop_path in self.running_apps: + print(f"Приложение {self.current_selected_app.get('name')} уже запущено.") + return - # Удаляем параметры (%F и подобные) + try: + command_parts = shlex.split(command_str) clean_command = [part for part in command_parts if not part.startswith('%')] if debug: @@ -3011,13 +3081,39 @@ class WineHelperGUI(QMainWindow): QMessageBox.critical(self, "Ошибка", f"Не удалось модифицировать команду для отладки: {e}") return - # Удаление префикса + process = QProcess(self) + env = QProcessEnvironment.systemEnvironment() + + cmd_start_index = 0 + if clean_command and clean_command[0] == 'env': + cmd_start_index = 1 + while cmd_start_index < len(clean_command) and '=' in clean_command[cmd_start_index]: + key, value = clean_command[cmd_start_index].split('=', 1) + env.insert(key, value.strip('"\'')) + cmd_start_index += 1 + + if cmd_start_index >= len(clean_command): + raise ValueError("Не найдена команда для выполнения в строке Exec.") + + program = clean_command[cmd_start_index] + arguments = clean_command[cmd_start_index + 1:] + + process.setProcessEnvironment(env) + process.finished.connect(lambda: self._on_app_process_finished(desktop_path)) + try: - subprocess.Popen(clean_command) - print(f"Запущено: {' '.join(clean_command)}") + process.start(program, arguments) + if not process.waitForStarted(3000): + raise RuntimeError(f"Не удалось запустить процесс: {process.errorString()}") + + self.running_apps[desktop_path] = process + self.run_button.setText("Остановить") + print(f"Запущено: {program} {' '.join(arguments)}") except Exception as e: QMessageBox.critical(self, "Ошибка запуска", - f"Не удалось запустить команду:\n{' '.join(clean_command)}\n\nОшибка: {str(e)}") + f"Не удалось запустить команду:\n{command_str}\n\nОшибка: {str(e)}") + if desktop_path in self.running_apps: + del self.running_apps[desktop_path] except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}")