# This script adds ScriptWorker and a script execution tab to gui.py import os gui_path = "src/virtualbox_client/gui.py" with open(gui_path, "r", encoding="utf-8") as f: content = f.read() # 1. Add ScriptWorker before WorkspaceWorker old_worker = "class WorkspaceWorker(QObject):" new_worker = """class ScriptWorker(QObject): log_signal = Signal(str, str) finished_signal = Signal(bool) error_signal = Signal(str) def __init__(self, config: dict, local_path: str): super().__init__() self.config = config self.local_path = local_path self.cancel_event = threading.Event() def cancel(self): self.cancel_event.set() def run(self): ssh_only = self.config.get("ssh_only", False) _com_initialized = False if not ssh_only: try: import ctypes ctypes.windll.ole32.CoInitializeEx(None, 0) _com_initialized = True except Exception: pass try: if ssh_only: from virtualbox_client.utils.ssh_manager import SSHManager vm = SSHManager( self.config["ssh_host"], self.config["ssh_port"], self.config["ssh_user"], self.config["ssh_pass"], ) else: from virtualbox_client.utils.vbox_ssh_manager import VBoxSSHManager vm = VBoxSSHManager( self.config["vm_name"], self.config["ssh_host"], self.config["ssh_port"], self.config["ssh_user"], self.config["ssh_pass"], ) vm.set_cancel_event(self.cancel_event) if self.cancel_event.is_set(): return import builtins original_print = print def custom_print(*args, **kwargs): msg = " ".join(str(a) for a in args) self.log_signal.emit(msg, "info") original_print(*args, **kwargs) builtins.print = custom_print try: import os as _os script_name = _os.path.basename(self.local_path) remote_path = "/tmp/" + script_name log_path = "/tmp/" + script_name + ".log" local_log_path = script_name + ".log" self.log_signal.emit("[1/3] Upload: " + self.local_path + " -> " + remote_path, "info") if not vm.upload_file(self.local_path, remote_path): self.log_signal.emit("Upload failed", "error") return self.log_signal.emit("[2/3] Execute: " + remote_path, "info") cmd = ( "chmod +x " + remote_path + " && " "echo '" + self.config["ssh_pass"] + "' | sudo -S " + remote_path + " 2>&1 | " "tee " + log_path ) out, err, code = vm.exec_command(cmd, get_pty=False) self.log_signal.emit("Exit code: " + str(code), "info") self.log_signal.emit("[3/3] Download log: " + log_path + " -> " + local_log_path, "info") if vm.download_file(log_path, local_log_path): self.log_signal.emit("Log saved: " + local_log_path, "success") else: self.log_signal.emit("Tee log download failed, saving terminal output", "warning") if out: with open(local_log_path, "w") as f: f.write(out) self.log_signal.emit("Output saved: " + local_log_path, "success") vm.exec_command("rm -f \\"" + remote_path + "\\" \\"" + log_path + "\\"") self.log_signal.emit("Remote temp files cleaned", "info") self.log_signal.emit("Script execution complete!", "success") self.finished_signal.emit(True) finally: builtins.print = original_print except ImportError as e: self.log_signal.emit("Missing dependency: " + str(e), "error") self.error_signal.emit(str(e)) except Exception as e: self.log_signal.emit("Script error: " + str(e), "error") self.error_signal.emit(str(e)) finally: if _com_initialized: try: import ctypes ctypes.windll.ole32.CoUninitialize() except Exception: pass """ + old_worker content = content.replace(old_worker, new_worker) # 2. Add script tab after file transfer tab old_tab_end = ' file_layout.addStretch()\n self.tab_widget.addTab(file_tab, "\U0001f4c1 \u6587\u4ef6\u4f20\u8f93")' new_tab_end = ''' file_layout.addStretch() self.tab_widget.addTab(file_tab, "\U0001f4c1 \u6587\u4ef6\u4f20\u8f93") # ====== Script Execution Tab ====== script_tab = QWidget() script_layout = QVBoxLayout(script_tab) script_layout.setContentsMargins(8, 8, 8, 8) script_group = QGroupBox("Upload and Execute Script") script_group_layout = QVBoxLayout(script_group) script_path_layout = QHBoxLayout() self.script_path_edit = QLineEdit(placeholderText="Select local .sh script file...") script_path_layout.addWidget(self.script_path_edit) self.script_browse_btn = QPushButton("Browse") self.script_browse_btn.setFixedWidth(80) self.script_browse_btn.clicked.connect(self._browse_script) script_path_layout.addWidget(self.script_browse_btn) script_group_layout.addWidget(QLabel("Local Script:")) script_group_layout.addLayout(script_path_layout) self.run_script_btn = QPushButton("Upload and Execute Script") self.run_script_btn.setStyleSheet(""" QPushButton { background-color: #8e44ad; color: white; padding: 10px 30px; font-size: 14px; font-weight: bold; border: none; border-radius: 6px; } QPushButton:hover { background-color: #9b59b6; } QPushButton:disabled { background-color: #555; color: #999; } """) self.run_script_btn.clicked.connect(self._run_script) script_group_layout.addWidget(self.run_script_btn) script_layout.addWidget(script_group) desc_group = QGroupBox("How it works") desc_layout = QVBoxLayout(desc_group) desc_label = QLabel( "1. Select a local .sh script file\\n" "2. Click button to auto:\\n" " Upload script to /tmp/\\n" " Execute with sudo\\n" " Download execution log\\n" " Clean remote temp files" ) desc_label.setStyleSheet("color: #aaa; font-size: 12px; line-height: 1.6;") desc_layout.addWidget(desc_label) script_layout.addWidget(desc_group) script_layout.addStretch() self.tab_widget.addTab(script_tab, "Script")''' content = content.replace(old_tab_end, new_tab_end) # 3. Update _set_ui_running old_running = """ def _set_ui_running(self, running: bool): self.run_btn.setEnabled(not running) self.cancel_btn.setEnabled(running) self.task_input.setEnabled(not running) if self._config_panel: self._config_panel.set_enabled(not running)""" new_running = """ def _set_ui_running(self, running: bool): self.run_btn.setEnabled(not running) self.cancel_btn.setEnabled(running) self.task_input.setEnabled(not running) self.run_script_btn.setEnabled(not running) self.script_browse_btn.setEnabled(not running) self.script_path_edit.setEnabled(not running) if self._config_panel: self._config_panel.set_enabled(not running)""" content = content.replace(old_running, new_running) # 4. Add methods before MainWindow old_mw = "class MainWindow(QMainWindow):" new_mw = """ _script_thread = None _script_worker = None def _cleanup_script(self): if self._script_thread: try: self._script_thread.finished.disconnect() except (TypeError, RuntimeError): pass if self._script_thread.isRunning(): self._script_thread.quit() if not self._script_thread.wait(5000): self._script_thread.terminate() self._script_thread.wait(3000) self._script_thread = None if self._script_worker: try: self._script_worker.log_signal.disconnect() self._script_worker.finished_signal.disconnect() self._script_worker.error_signal.disconnect() except (TypeError, RuntimeError): pass self._script_worker = None def _browse_script(self): file_path, _ = QFileDialog.getOpenFileName( self, "Select script", "", "Shell scripts (*.sh);;All files (*)" ) if file_path: self.script_path_edit.setText(file_path) def _run_script(self): if not self._config_panel or not self._config_panel.validate(): return local_path = self.script_path_edit.text().strip() if not local_path: QMessageBox.warning(self, "Hint", "Please select a script file") return if not os.path.isfile(local_path): QMessageBox.warning(self, "Hint", "File not found: " + local_path) return self._cleanup_script() self._set_ui_running(True) self.tab_widget.setCurrentIndex(0) self.log_panel.clear_log() config = self._config_panel.get_config() self._script_worker = ScriptWorker(config, local_path) self._script_thread = QThread(self) self._script_worker.moveToThread(self._script_thread) self._script_worker.log_signal.connect(self.log_panel.append_log) self._script_worker.finished_signal.connect(self._on_script_finished) self._script_worker.error_signal.connect(self._on_script_error) self._script_thread.started.connect(self._script_worker.run) self._script_thread.finished.connect(self._cleanup_script) self._script_thread.start() def _on_script_finished(self, success: bool): self._set_ui_running(False) if success: self.log_panel.append_log("Script execution completed", "success") def _on_script_error(self, error_msg: str): self._set_ui_running(False) self.log_panel.append_log("Script error: " + error_msg, "error") """ + old_mw content = content.replace(old_mw, new_mw) with open(gui_path, "w", encoding="utf-8") as f: f.write(content) print("Done")