def write(self, file_name, storage_device, mesh_data): Logger.log("i", "In X3gWriter.write") if "x3g" in file_name: scene = Application.getInstance().getController().getScene() gcode_list = getattr(scene, "gcode_list") if gcode_list: # f = storage_device.openFile(file_name, "wt") Logger.log("i", "Writing X3g to file %s", file_name) p = QProcess() p.setReadChannel(1) p.setStandardOutputFile(file_name) p.start("gpx", ["-i"]) p.waitForStarted() for gcode in gcode_list: p.write(gcode) if p.canReadLine(): Logger.log("i", "gpx: %s", p.readLine().data().decode("utf-8")) p.closeWriteChannel() if p.waitForFinished(): Logger.log("i", "gpx: %s", p.readAll().data().decode("utf-8")) p.close() # storage_device.closeFile(f) return True return False
def write(self, file_name, storage_device, mesh_data): Logger.log("i", "In X3gWriter.write") if "x3g" in file_name: scene = Application.getInstance().getController().getScene() gcode_list = getattr(scene, "gcode_list") if gcode_list: # f = storage_device.openFile(file_name, "wt") Logger.log("i", "Writing X3g to file %s", file_name) p = QProcess() p.setReadChannel(1) p.setStandardOutputFile(file_name) p.start("gpx", ["-i"]) p.waitForStarted() for gcode in gcode_list: p.write(gcode) if (p.canReadLine()): Logger.log("i", "gpx: %s", p.readLine().data().decode('utf-8')) p.closeWriteChannel() if p.waitForFinished(): Logger.log("i", "gpx: %s", p.readAll().data().decode('utf-8')) p.close() # storage_device.closeFile(f) return True return False
def run(self): for example in self.examples: print(example) self.example = example example_file = self.workDir + '\\inputs\\' + example cmd_single = run_cmd(self.source, example_file) self.cmd = cmd_single cmd = shell_cmd(cmd_single) p = QProcess() self.process = p p.start('cmd') p.waitForStarted(1000) p.write(bytes(cmd, 'GBK')) p.closeWriteChannel() p.waitForFinished() output = p.readAllStandardOutput() # print("output is",output) self.CheckFinished.emit( example, read_out(output, cmd_single) + read_out(p.readAllStandardError(), cmd_single)) # 以下三行代码仅限于最后一次作业 # t = verify_file('c.std.txt','c.txt') # self.CheckFinished.emit('文件比较',t) # os.system('del c.txt') self.AllFinished.emit()
def connect_speaker_device(self, address): if platform.system() == "Darwin": print("Pairing...") process = QProcess() process.setProcessChannelMode(QProcess.SeparateChannels) process.start(f"blueutil --pair {address}") process.waitForFinished() print("Connecting...") process.start(f"blueutil --connect {address}") process.waitForFinished() print("Connected!") elif platform.system() == "Linux": process = QProcess() process.setProcessChannelMode(QProcess.SeparateChannels) process.start("bluetoothctl") process.waitForStarted() process.waitForReadyRead() process.writeData(bytes("connect B1:20:B5:86:AA:B9\n", 'utf-8')) process.waitForBytesWritten() while True: process.waitForReadyRead(1500) data = bytes(process.readAll()).decode('utf-8').split(" ") print(data) if any("successful" in x for x in data): print("Connection successful") break elif any("Failed" in x for x in data): print(f"ERROR: bluez.failed") self.bluetooth_speaker_cb( BluetoothSpeakerError( "Bluetooth speaker daemon failed to start")) time.sleep(1.0) process.writeData(bytes("exit\n", 'utf-8')) process.waitForBytesWritten() process.waitForReadyRead(1500) process.closeWriteChannel() process.waitForFinished() process.close() print("Waiting for notify_connect to fire") else: print("Unsupported platform")
def clear_cached_speaker_device(self): if platform.system() == "Linux": print("Clearing Bluetooth device cache") process = QProcess() process.setProcessChannelMode(QProcess.SeparateChannels) process.start("bluetoothctl") process.waitForStarted() time.sleep(0.1) process.waitForReadyRead() print(bytes(process.readAll()).decode('utf-8')) process.writeData(bytes("remove B1:20:B5:86:AA:B9\n", 'utf-8')) process.waitForBytesWritten() time.sleep(0.1) process.waitForReadyRead() print(bytes(process.readAll()).decode('utf-8')) process.closeWriteChannel() process.waitForFinished() print("Cleared")
def load_cr3(path) -> QPixmap: """Extract the thumbnail from the image and initialize QPixmap""" process = QProcess() process.start(f"exiftool -b -JpgFromRaw {path}") process.waitForFinished() if process.exitStatus() != QProcess.NormalExit or process.exitCode() != 0: stderr = process.readAllStandardError() raise ValueError(f"Error calling exiftool: '{stderr.data().decode()}'") handler = QImageReader(process, "jpeg".encode()) handler.setAutoTransform(True) process.closeWriteChannel() process.terminate() # Extract QImage from QImageReader and convert to QPixmap pixmap = QPixmap() pixmap.convertFromImage(handler.read()) return pixmap
def load_frame(path) -> QPixmap: """Extract the first frame from the video and initialize QPixmap""" process = QProcess() process.start( f"ffmpeg -loglevel quiet -i {path} -vframes 1 -f image2 pipe:1") process.waitForFinished() if process.exitStatus() != QProcess.NormalExit or process.exitCode() != 0: stderr = process.readAllStandardError() raise ValueError(f"Error calling ffmpeg: '{stderr.data().decode()}'") handler = QImageReader(process, "jpeg".encode()) handler.setAutoTransform(True) process.closeWriteChannel() process.terminate() # Extract QImage from QImageReader and convert to QPixmap pixmap = QPixmap() pixmap.convertFromImage(handler.read()) return pixmap
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _output_messages: Show output as messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, output_messages=False, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._output_messages = output_messages self._started = False self.cmd = None self.args = None self.final_stdout: str = "" self.final_stderr: str = "" self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot(QProcess.ProcessError) def _on_error(self, error): """Show a message if there was an error while spawning.""" if error == QProcess.Crashed and not utils.is_windows: # Already handled via ExitStatus in _on_finished return what = f"{self._what} {self.cmd!r}" error_descriptions = { QProcess.FailedToStart: f"{what.capitalize()} failed to start", QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", QProcess.WriteError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 # However, it looks like those strings aren't actually translated? known_errors = ['No such file or directory', 'Permission denied'] if (': ' in error_string and # pragma: no branch error_string.split(': ', maxsplit=1)[1] in known_errors): msg += f'\n(Hint: Make sure {self.cmd!r} exists and is executable)' message.error(msg) @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) encoding = locale.getpreferredencoding(do_setlocale=False) stderr = self._proc.readAllStandardError().data().decode( encoding, 'replace') stdout = self._proc.readAllStandardOutput().data().decode( encoding, 'replace') if self._output_messages: if stdout: message.info(stdout.strip()) if stderr: message.error(stderr.strip()) if status == QProcess.CrashExit: exitinfo = "{} crashed.".format(self._what.capitalize()) message.error(exitinfo) elif status == QProcess.NormalExit and code == 0: exitinfo = "{} exited successfully.".format( self._what.capitalize()) if self.verbose: message.info(exitinfo) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. exitinfo = ("{} exited with status {}, see :messages for " "details.").format(self._what.capitalize(), code) message.error(exitinfo) if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) self.final_stdout = stdout self.final_stderr = stderr def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() spawn_string = ("{}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format( exitinfo, stdout, stderr) return spawn_string @pyqtSlot() def _on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._proc.closeWriteChannel() def start_detached(self, cmd, args): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, None) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self._what)) return False log.procs.debug("Process started.") self._started = True return True def exit_status(self): return self._proc.exitStatus()
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._started = False self.cmd = None self.args = None self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot() def _on_error(self): """Show a message if there was an error while spawning.""" msg = self._proc.errorString() message.error("Error while spawning {}: {}".format(self._what, msg)) @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) encoding = locale.getpreferredencoding(do_setlocale=False) stderr = bytes(self._proc.readAllStandardError()).decode( encoding, 'replace') stdout = bytes(self._proc.readAllStandardOutput()).decode( encoding, 'replace') if status == QProcess.CrashExit: exitinfo = "{} crashed!".format(self._what.capitalize()) message.error(exitinfo) elif status == QProcess.NormalExit and code == 0: exitinfo = "{} exited successfully.".format( self._what.capitalize()) if self.verbose: message.info(exitinfo) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. exitinfo = ("{} exited with status {}, see :messages for " "details.").format(self._what.capitalize(), code) message.error(exitinfo) if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) glimpsescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() spawn_string = ("{}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format(exitinfo, stdout, stderr) return spawn_string @pyqtSlot() def _on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._proc.closeWriteChannel() def start_detached(self, cmd, args): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, None) if not ok: message.error("Error while spawning {}".format(self._what)) return False log.procs.debug("Process started.") self._started = True return True def exit_status(self): return self._proc.exitStatus()
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. running: Whether the underlying process is started. what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. _output_messages: Show output as messages. _proc: The underlying QProcess. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__( self, what: str, *, verbose: bool = False, additional_env: Mapping[str, str] = None, output_messages: bool = False, parent: QObject = None, ): super().__init__(parent) self.what = what self.verbose = verbose self._output_messages = output_messages self.outcome = ProcessOutcome(what=what) self.cmd: Optional[str] = None self.args: Optional[Sequence[str]] = None self.pid: Optional[int] = None self.stdout: str = "" self.stderr: str = "" self._cleanup_timer = usertypes.Timer(self, 'process-cleanup') self._cleanup_timer.setTimerType(Qt.VeryCoarseTimer) self._cleanup_timer.setInterval(3600 * 1000) # 1h self._cleanup_timer.timeout.connect(self._on_cleanup_timer) self._cleanup_timer.setSingleShot(True) self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) self._proc.errorOccurred.connect(self.error) self._proc.finished.connect(self._on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self._on_started) self._proc.started.connect(self.started) self._proc.readyReadStandardOutput.connect(self._on_ready_read_stdout) self._proc.readyReadStandardError.connect(self._on_ready_read_stderr) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) def __str__(self) -> str: if self.cmd is None or self.args is None: return f'<unknown {self.what} command>' return ' '.join(shlex.quote(e) for e in [self.cmd] + list(self.args)) def _decode_data(self, qba: QByteArray) -> str: """Decode data coming from a process.""" encoding = locale.getpreferredencoding(do_setlocale=False) return qba.data().decode(encoding, 'replace') def _process_text(self, data: QByteArray, attr: str) -> None: """Process new stdout/stderr text. Arguments: data: The new process data. attr: Either 'stdout' or 'stderr'. """ text = self._decode_data(data) if '\r' in text and not utils.is_windows: # Crude handling of CR for e.g. progress output. # Discard everything before the last \r in the new input, then discard # everything after the last \n in self.stdout/self.stderr. text = text.rsplit('\r', maxsplit=1)[-1] existing = getattr(self, attr) if '\n' in existing: new = existing.rsplit('\n', maxsplit=1)[0] + '\n' else: new = '' setattr(self, attr, new) if attr == 'stdout': self.stdout += text elif attr == 'stderr': self.stderr += text else: raise utils.Unreachable(attr) @pyqtSlot() def _on_ready_read_stdout(self) -> None: if not self._output_messages: return self._process_text(self._proc.readAllStandardOutput(), 'stdout') message.info(self._elide_output(self.stdout), replace=f"stdout-{self.pid}") @pyqtSlot() def _on_ready_read_stderr(self) -> None: if not self._output_messages: return self._process_text(self._proc.readAllStandardError(), 'stderr') message.error(self._elide_output(self.stderr), replace=f"stderr-{self.pid}") @pyqtSlot(QProcess.ProcessError) def _on_error(self, error: QProcess.ProcessError) -> None: """Show a message if there was an error while spawning.""" if error == QProcess.Crashed and not utils.is_windows: # Already handled via ExitStatus in _on_finished return what = f"{self.what} {self.cmd!r}" error_descriptions = { QProcess.FailedToStart: f"{what.capitalize()} failed to start", QProcess.Crashed: f"{what.capitalize()} crashed", QProcess.Timedout: f"{what.capitalize()} timed out", QProcess.WriteError: f"Write error for {what}", QProcess.ReadError: f"Read error for {what}", } error_string = self._proc.errorString() msg = ': '.join([error_descriptions[error], error_string]) # We can't get some kind of error code from Qt... # https://bugreports.qt.io/browse/QTBUG-44769 # However, it looks like those strings aren't actually translated? known_errors = ['No such file or directory', 'Permission denied'] if (': ' in error_string and # pragma: no branch error_string.split(': ', maxsplit=1)[1] in known_errors): msg += f'\nHint: Make sure {self.cmd!r} exists and is executable' if version.is_flatpak(): msg += ' inside the Flatpak container' message.error(msg) def _elide_output(self, output: str) -> str: """Shorten long output before showing it.""" output = output.strip() lines = output.splitlines() count = len(lines) threshold = 20 if count > threshold: lines = [ f'[{count - threshold} lines hidden, see :process for the full output]' ] + lines[-threshold:] output = '\n'.join(lines) return output @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code: int, status: QProcess.ExitStatus) -> None: """Show a message when the process finished.""" log.procs.debug("Process finished with code {}, status {}.".format( code, status)) self.outcome.running = False self.outcome.code = code self.outcome.status = status self.stderr += self._decode_data(self._proc.readAllStandardError()) self.stdout += self._decode_data(self._proc.readAllStandardOutput()) if self._output_messages: if self.stdout: message.info( self._elide_output(self.stdout), replace=f"stdout-{self.pid}") if self.stderr: message.error( self._elide_output(self.stderr), replace=f"stderr-{self.pid}") if self.outcome.was_successful(): if self.verbose: message.info(str(self.outcome)) self._cleanup_timer.start() else: if self.stdout: log.procs.error("Process stdout:\n" + self.stdout.strip()) if self.stderr: log.procs.error("Process stderr:\n" + self.stderr.strip()) message.error(str(self.outcome) + " See :process for details.") @pyqtSlot() def _on_started(self) -> None: """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self.outcome.running self.outcome.running = True def _pre_start(self, cmd: str, args: Sequence[str]) -> None: """Prepare starting of a QProcess.""" if self.outcome.running: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args log.procs.debug(f"Executing: {self}") if self.verbose: message.info(f'Executing: {self}') def start(self, cmd: str, args: Sequence[str]) -> None: """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._post_start() self._proc.closeWriteChannel() def start_detached(self, cmd: str, args: Sequence[str]) -> bool: """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, self.pid = self._proc.startDetached( cmd, args, None) # type: ignore[call-arg] if not ok: message.error("Error while spawning {}".format(self.what)) return False log.procs.debug("Process started.") self.outcome.running = True self._post_start() return True def _post_start(self) -> None: """Register this process and remember the process ID after starting.""" self.pid = self._proc.processId() all_processes[self.pid] = self global last_pid last_pid = self.pid @pyqtSlot() def _on_cleanup_timer(self) -> None: """Remove the process from all registered processes.""" log.procs.debug(f"Cleaning up data for {self.pid}") assert self.pid in all_processes all_processes[self.pid] = None self.deleteLater() def terminate(self, kill: bool = False) -> None: """Terminate or kill the process.""" if kill: self._proc.kill() else: self._proc.terminate()
class AsyncProcess: """ Wraps a QProcess and provides a neat await-able interface. This is not "cross-task-safe", i.e. one AsyncProcess should not be manipulated concurrently by more than one task. """ def __init__(self, args=None, qprocess=None, scheduler=None): self._scheduler = scheduler if qprocess: self._process = qprocess assert not args else: self._process = QProcess() self._process.setProgram(args[0]) self._process.setArguments(args[1:]) self._current_error_occured = self._error_outside_await self._process.errorOccurred.connect(self._error_outside_await) self._stored_error = None def _error_outside_await(self, error): self._stored_error = error def _set_error_occured(self, swapin): swapout = self._current_error_occured self._process.errorOccurred.disconnect(swapout) self._process.errorOccurred.connect(swapin) self._current_error_occured = swapin def _error_handling_future(self, source_signal, result_callback, exit_callback=None) -> Future: """ Create future responding to QProcess errors. *result_callback* is connected to *source_signal*. *exit_callback* is connected to QProcess.finished. Any error occurring until completion of the future will set an exception on the future and thus consume the error. Signal connections are undone when the future is done. """ def error_occurred(error): fut.set_exception(AsyncProcessError(error)) def process_finished(*args): exit_callback() def disconnect(fut): source_signal.disconnect(result_callback) if exit_callback: self._process.finished.disconnect(process_finished) self._set_error_occured(self._error_outside_await) fut = asynker.Future(self._scheduler) source_signal.connect(result_callback) if exit_callback: self._process.finished.connect(process_finished) self._set_error_occured(error_occurred) fut.add_done_callback(disconnect) # Check if there already happened an error if self._stored_error is not None: error_occurred(self._stored_error) self._stored_error = None return fut def start(self, mode=QProcess.ReadWrite) -> Future: """ Start the process. *mode* is passed to QProcess.start(). """ def started(): fut.set_result(self._process.processId()) assert self._process.state() == QProcess.NotRunning fut = self._error_handling_future(self._process.started, started) self._process.start(mode) return fut def _read(self, ready_read_signal, read_method) -> Future: def data_available(): fut.set_result(read_method().data()) fut = self._error_handling_future(ready_read_signal, data_available, data_available) # Clear out buffered data immediately data = read_method().data() if data: fut.set_result(data) elif not data and self._process.state() == QProcess.NotRunning: fut.set_result(b'') return fut def read_stdout(self) -> Future: """ Read some binary data from the process's stdout channel. """ return self._read(self._process.readyReadStandardOutput, self._process.readAllStandardOutput) def read_stderr(self) -> Future: """ Read some binary data from the process's stdout channel. """ return self._read(self._process.readyReadStandardError, self._process.readAllStandardError) def write(self, data) -> Future: """ Write *data* to the standard input of the attached process. """ def bytes_written(n=None): nonlocal pending_bytes if n is None and pending_bytes: fut.set_exception(AsyncProcessLostWrite) elif n: pending_bytes -= n if not pending_bytes: fut.set_result(None) assert self._process.state() == QProcess.Running fut = self._error_handling_future(self._process.bytesWritten, bytes_written, bytes_written) pending_bytes = len(data) amount = self._process.write(data) if amount == -1: fut.set_exception(OSError) pending_bytes -= amount if not pending_bytes: fut.set_result(None) return fut def write_eof(self): """ Close (send EOF to) the standard input of the attached process. """ self._process.closeWriteChannel() def finish(self) -> Future: """ Wait until the process exits. Returns an (exit_code, QProcess.ExitStatus) tuple. """ def finished(exit_code, exit_status): fut.set_result((exit_code, exit_status)) fut = self._error_handling_future(self._process.finished, finished) if self._process.state() == QProcess.NotRunning: finished(self._process.exitCode(), self._process.exitStatus()) return fut def running(self): return self._process.state() == QProcess.Running
class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._started = False self.cmd = None self.args = None self._proc = QProcess(self) self._proc.error.connect(self.on_error) self._proc.error.connect(self.error) self._proc.finished.connect(self.on_finished) self._proc.finished.connect(self.finished) self._proc.started.connect(self.on_started) self._proc.started.connect(self.started) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot(QProcess.ProcessError) def on_error(self, error): """Show a message if there was an error while spawning.""" msg = ERROR_STRINGS[error] message.error("Error while spawning {}: {}".format(self._what, msg)) @pyqtSlot(int, QProcess.ExitStatus) def on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) if status == QProcess.CrashExit: message.error("{} crashed!".format(self._what.capitalize())) elif status == QProcess.NormalExit and code == 0: if self.verbose: message.info("{} exited successfully.".format( self._what.capitalize())) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. message.error("{} exited with status {}.".format( self._what.capitalize(), code)) stderr = bytes(self._proc.readAllStandardError()).decode('utf-8') stdout = bytes(self._proc.readAllStandardOutput()).decode('utf-8') if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) @pyqtSlot() def on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args, mode=None): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) if mode is None: self._proc.start(cmd, args) else: self._proc.start(cmd, args, mode) self._proc.closeWriteChannel() def start_detached(self, cmd, args, cwd=None): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, cwd) if ok: log.procs.debug("Process started.") self._started = True else: message.error("Error while spawning {}: {}".format( self._what, ERROR_STRINGS[self._proc.error()])) def exit_status(self): return self._proc.exitStatus()
class PlainTextEdit(QPlainTextEdit): commandSignal = pyqtSignal(str) commandZPressed = pyqtSignal(str) def __init__(self, parent=None, movable=False): super(PlainTextEdit, self).__init__() self.installEventFilter(self) self.setAcceptDrops(True) QApplication.setCursorFlashTime(1000) self.process = QProcess() self.process.readyReadStandardError.connect( self.onReadyReadStandardError) self.process.readyReadStandardOutput.connect( self.onReadyReadStandardOutput) self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname()) + ":" + str(os.getcwd()) + "$ ") self.appendPlainText(self.name) self.commands = [ ] # This is a list to track what commands the user has used so we could display them when # up arrow key is pressed self.tracker = 0 self.setStyleSheet( "QPlainTextEdit{background-color: #212121; color: #f3f3f3; padding: 8;}" ) self.verticalScrollBar().setStyleSheet("background-color: #212121;") self.text = None self.setFont(QFont("Noto Sans Mono", 8)) self.previousCommandLength = 0 def eventFilter(self, source, event): if (event.type() == QEvent.DragEnter): event.accept() print('DragEnter') return True elif (event.type() == QEvent.Drop): print('Drop') self.setDropEvent(event) return True else: return False ### super(QPlainTextEdit).eventFilter(event) def setDropEvent(self, event): if event.mimeData().hasUrls(): f = str(event.mimeData().urls()[0].toLocalFile()) self.insertPlainText(f) event.accept() elif event.mimeData().hasText(): ft = event.mimeData().text() print("text:", ft) self.insertPlainText(ft) event.accept() else: event.ignore() def keyPressEvent(self, e): cursor = self.textCursor() if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_A: return if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_Z: self.commandZPressed.emit("True") return if e.modifiers() == Qt.ControlModifier and e.key() == Qt.Key_C: self.process.kill() self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname()) + ":" + str(os.getcwd()) + "$ ") self.appendPlainText("process cancelled") self.appendPlainText(self.name) self.textCursor().movePosition(QTextCursor.End) return if e.key() == Qt.Key_Return: ### 16777220: # This is the ENTER key text = self.textCursor().block().text() if text == self.name + text.replace(self.name, "") and text.replace( self.name, "" ) != "": # This is to prevent adding in commands that were not meant to be added in self.commands.append(text.replace(self.name, "")) # print(self.commands) self.handle(text) self.commandSignal.emit(text) self.appendPlainText(self.name) return if e.key() == Qt.Key_Up: try: if self.tracker != 0: cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() self.appendPlainText(self.name) self.insertPlainText(self.commands[self.tracker]) self.tracker -= 1 except IndexError: self.tracker = 0 return if e.key() == Qt.Key_Down: try: cursor.select(QTextCursor.BlockUnderCursor) cursor.removeSelectedText() self.appendPlainText(self.name) self.insertPlainText(self.commands[self.tracker]) self.tracker += 1 except IndexError: self.tracker = 0 if e.key() == Qt.Key_Backspace: ### 16777219: if cursor.positionInBlock() <= len(self.name): return else: cursor.deleteChar() super().keyPressEvent(e) cursor = self.textCursor() e.accept() def ispressed(self): return self.pressed def onReadyReadStandardError(self): self.error = self.process.readAllStandardError().data().decode() self.appendPlainText(self.error.strip('\n')) def onReadyReadStandardOutput(self): self.result = self.process.readAllStandardOutput().data().decode() self.appendPlainText(self.result.strip('\n')) self.state = self.process.state() # print(self.result) def run(self, command): """Executes a system command.""" if self.process.state() != 2: self.process.start(command) self.process.waitForFinished() self.textCursor().movePosition(QTextCursor.End) def handle(self, command): # print("begin handle") """Split a command into list so command echo hi would appear as ['echo', 'hi']""" real_command = command.replace(self.name, "") if command == "True" and self.process.state() == 2: self.process.kill() self.appendPlainText("Program execution killed, press enter") if real_command.startswith("python"): pass command_list = real_command.split() if real_command != "" else None """Now we start implementing some commands""" if real_command == "clear": self.clear() elif command_list is not None and command_list[0] == "echo": self.appendPlainText(" ".join(command_list[1:])) elif real_command == "exit": quit() elif command_list is not None and command_list[0] == "cd" and len( command_list) > 1: try: os.chdir(" ".join(command_list[1:])) self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname()) + ":" + str(os.getcwd()) + "$ ") self.textCursor().movePosition(QTextCursor.End) except FileNotFoundError as E: self.appendPlainText(str(E)) elif command_list is not None and len( command_list) == 1 and command_list[0] == "cd": os.chdir(str(Path.home())) self.name = (str(getpass.getuser()) + "@" + str(socket.gethostname()) + ":" + str(os.getcwd()) + "$ ") self.textCursor().movePosition(QTextCursor.End) elif self.process.state() == 2: self.process.write(real_command.encode()) self.process.closeWriteChannel() elif command == self.name + real_command: self.run(real_command)
class HgClient(QObject): """ Class implementing the Mercurial command server interface. """ InputFormat = ">I" OutputFormat = ">cI" OutputFormatSize = struct.calcsize(OutputFormat) ReturnFormat = ">i" Channels = (b"I", b"L", b"o", b"e", b"r", b"d") def __init__(self, repoPath, encoding, vcs, parent=None): """ Constructor @param repoPath root directory of the repository (string) @param encoding encoding to be used by the command server (string) @param vcs reference to the VCS object (Hg) @param parent reference to the parent object (QObject) """ super(HgClient, self).__init__(parent) self.__server = None self.__started = False self.__version = None self.__encoding = vcs.getEncoding() self.__cancel = False self.__commandRunning = False self.__repoPath = repoPath # generate command line and environment self.__serverArgs = vcs.initCommand("serve") self.__serverArgs.append("--cmdserver") self.__serverArgs.append("pipe") self.__serverArgs.append("--config") self.__serverArgs.append("ui.interactive=True") if repoPath: self.__serverArgs.append("--repository") self.__serverArgs.append(repoPath) if encoding: self.__encoding = encoding if "--encoding" in self.__serverArgs: # use the defined encoding via the environment index = self.__serverArgs.index("--encoding") del self.__serverArgs[index:index + 2] def startServer(self): """ Public method to start the command server. @return tuple of flag indicating a successful start (boolean) and an error message (string) in case of failure """ self.__server = QProcess() self.__server.setWorkingDirectory(self.__repoPath) # connect signals self.__server.finished.connect(self.__serverFinished) prepareProcess(self.__server, self.__encoding) self.__server.start('hg', self.__serverArgs) serverStarted = self.__server.waitForStarted(5000) if not serverStarted: return False, self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('hg') self.__server.setReadChannel(QProcess.StandardOutput) ok, error = self.__readHello() self.__started = ok return ok, error def stopServer(self): """ Public method to stop the command server. """ if self.__server is not None: self.__server.closeWriteChannel() res = self.__server.waitForFinished(5000) if not res: self.__server.terminate() res = self.__server.waitForFinished(3000) if not res: self.__server.kill() self.__server.waitForFinished(3000) self.__started = False self.__server.deleteLater() self.__server = None def restartServer(self): """ Public method to restart the command server. @return tuple of flag indicating a successful start (boolean) and an error message (string) in case of failure """ self.stopServer() return self.startServer() def __readHello(self): """ Private method to read the hello message sent by the command server. @return tuple of flag indicating success (boolean) and an error message in case of failure (string) """ ch, msg = self.__readChannel() if not ch: return False, self.tr("Did not receive the 'hello' message.") elif ch != "o": return False, self.tr("Received data on unexpected channel.") msg = msg.split("\n") if not msg[0].startswith("capabilities: "): return False, self.tr( "Bad 'hello' message, expected 'capabilities: '" " but got '{0}'.").format(msg[0]) self.__capabilities = msg[0][len('capabilities: '):] if not self.__capabilities: return False, self.tr("'capabilities' message did not contain" " any capability.") self.__capabilities = set(self.__capabilities.split()) if "runcommand" not in self.__capabilities: return False, "'capabilities' did not contain 'runcommand'." if not msg[1].startswith("encoding: "): return False, self.tr( "Bad 'hello' message, expected 'encoding: '" " but got '{0}'.").format(msg[1]) encoding = msg[1][len('encoding: '):] if not encoding: return False, self.tr("'encoding' message did not contain" " any encoding.") self.__encoding = encoding return True, "" def __serverFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__started = False def __readChannel(self): """ Private method to read data from the command server. @return tuple of channel designator and channel data (string, integer or string or bytes) """ if self.__server.bytesAvailable() > 0 or \ self.__server.waitForReadyRead(10000): data = bytes(self.__server.peek(HgClient.OutputFormatSize)) if not data or len(data) < HgClient.OutputFormatSize: return "", "" channel, length = struct.unpack(HgClient.OutputFormat, data) channel = channel.decode(self.__encoding) if channel in "IL": self.__server.read(HgClient.OutputFormatSize) return channel, length else: if self.__server.bytesAvailable() < \ HgClient.OutputFormatSize + length: return "", "" self.__server.read(HgClient.OutputFormatSize) data = self.__server.read(length) if channel == "r": return (channel, data) else: return (channel, str(data, self.__encoding, "replace")) else: return "", "" def __writeDataBlock(self, data): """ Private slot to write some data to the command server. @param data data to be sent (string) """ if not isinstance(data, bytes): data = data.encode(self.__encoding) self.__server.write( QByteArray(struct.pack(HgClient.InputFormat, len(data)))) self.__server.write(QByteArray(data)) self.__server.waitForBytesWritten() def __runcommand(self, args, inputChannels, outputChannels): """ Private method to run a command in the server (low level). @param args list of arguments for the command (list of string) @param inputChannels dictionary of input channels. The dictionary must have the keys 'I' and 'L' and each entry must be a function receiving the number of bytes to write. @param outputChannels dictionary of output channels. The dictionary must have the keys 'o' and 'e' and each entry must be a function receiving the data. @return result code of the command, -1 if the command server wasn't started or -10, if the command was canceled (integer) @exception RuntimeError raised to indicate an unexpected command channel """ if not self.__started: return -1 self.__server.write(QByteArray(b'runcommand\n')) self.__writeDataBlock('\0'.join(args)) while True: QCoreApplication.processEvents() if self.__cancel: return -10 if self.__server is None: return -1 if self.__server is None or self.__server.bytesAvailable() == 0: QThread.msleep(50) continue channel, data = self.__readChannel() # input channels if channel in inputChannels: input = inputChannels[channel](data) if channel == "L": # echo the input to the output if it was a prompt outputChannels["o"](input) self.__writeDataBlock(input) # output channels elif channel in outputChannels: outputChannels[channel](data) # result channel, command is finished elif channel == "r": return struct.unpack(HgClient.ReturnFormat, data)[0] # unexpected but required channel elif channel.isupper(): raise RuntimeError( "Unexpected but required channel '{0}'.".format(channel)) # optional channels or no channel at all else: pass def __prompt(self, size, message): """ Private method to prompt the user for some input. @param size maximum length of the requested input (integer) @param message message sent by the server (string) @return data entered by the user (string) """ from .HgClientPromptDialog import HgClientPromptDialog input = "" dlg = HgClientPromptDialog(size, message) if dlg.exec_() == QDialog.Accepted: input = dlg.getInput() + '\n' return input def runcommand(self, args, prompt=None, input=None, output=None, error=None): """ Public method to execute a command via the command server. @param args list of arguments for the command (list of string) @keyparam prompt function to reply to prompts by the server. It receives the max number of bytes to return and the contents of the output channel received so far. @keyparam input function to reply to bulk data requests by the server. It receives the max number of bytes to return. @keyparam output function receiving the data from the server (string). If a prompt function is given, this parameter will be ignored. @keyparam error function receiving error messages from the server (string) @return output and errors of the command server (string). In case output and/or error functions were given, the respective return value will be an empty string. """ self.__commandRunning = True outputChannels = {} outputBuffer = None errorBuffer = None if prompt is not None or output is None: outputBuffer = io.StringIO() outputChannels["o"] = outputBuffer.write else: outputChannels["o"] = output if error: outputChannels["e"] = error else: errorBuffer = io.StringIO() outputChannels["e"] = errorBuffer.write inputChannels = {} if prompt is not None: def func(size): reply = prompt(size, outputBuffer.getvalue()) return reply inputChannels["L"] = func else: def myprompt(size): if outputBuffer is None: msg = self.tr("For message see output dialog.") else: msg = outputBuffer.getvalue() reply = self.__prompt(size, msg) return reply inputChannels["L"] = myprompt if input is not None: inputChannels["I"] = input self.__cancel = False self.__runcommand(args, inputChannels, outputChannels) if outputBuffer: out = outputBuffer.getvalue() else: out = "" if errorBuffer: err = errorBuffer.getvalue() else: err = "" self.__commandRunning = False return out, err def cancel(self): """ Public method to cancel the running command. """ self.__cancel = True self.restartServer() def wasCanceled(self): """ Public method to check, if the last command was canceled. @return flag indicating the cancel state (boolean) """ return self.__cancel def isExecuting(self): """ Public method to check, if the server is executing a command. @return flag indicating the execution of a command (boolean) """ return self.__commandRunning
class SevenZipExtractor(Extractor): sig_entry_extracted = pyqtSignal(str, str) def __init__(self, filename: str, outdir: str) -> None: super().__init__() self._filename = os.path.abspath(filename) self._outdir = outdir self._process: Optional[QProcess] = None self._errors: List[str] = [] self._error_summary = False self._result: Optional[ExtractorResult] = None def interrupt(self): if self._process is not None: self._process.terminate() # self._process.waitForBytesWritten(int msecs = 30000) # self._process.kill() def extract(self) -> ExtractorResult: assert self._process is None try: self._start_extract(self._outdir) self._process.waitForFinished(-1) assert self._result is not None return self._result except Exception as err: message = "{}: failure when extracting archive".format(self._filename) logger.exception(message) message += "\n\n" + traceback.format_exc() return ExtractorResult.failure(message) def _start_extract(self, outdir: str) -> None: program = "7z" argv = ["x", "-ba", "-bb1", "-bd", "-aos", "-o" + outdir, self._filename] logger.debug("SevenZipExtractorWorker: launching %s %s", program, argv) self._process = QProcess() self._process.setProgram(program) self._process.setArguments(argv) self._process.readyReadStandardOutput.connect(self._on_ready_read_stdout) self._process.readyReadStandardError.connect(self._on_ready_read_stderr) self._process.finished.connect(self._on_process_finished) self._process.start() self._process.closeWriteChannel() def _on_process_finished(self, exit_code, exit_status): self._process.setCurrentReadChannel(QProcess.StandardOutput) for line in os.fsdecode(self._process.readAll().data()).splitlines(): self._process_stdout(line) self._process.setCurrentReadChannel(QProcess.StandardError) for line in os.fsdecode(self._process.readAll().data()).splitlines(): self._process_stderr(line) if self._errors != []: message = "7-Zip: " + "\n".join(self._errors) else: message = "" if message: logger.error("SevenZipExtractorWorker: errors: %s", message) if exit_status != QProcess.NormalExit or exit_code != 0: logger.error("SevenZipExtractorWorker: something went wrong: %s %s", exit_code, exit_status) self._result = ExtractorResult.failure(message) else: logger.debug("SevenZipExtractorWorker: finished successfully: %s %s", exit_code, exit_status) self._result = ExtractorResult.success() def _process_stdout(self, line): if line.startswith("- "): entry = line[2:] self.sig_entry_extracted.emit(entry, os.path.join(self._outdir, entry)) def _process_stderr(self, line): if line == "ERRORS:": self._error_summary = True else: if self._error_summary: if line != "": self._errors.append(line) def _on_ready_read_stdout(self) -> None: assert self._process is not None while self._process.canReadLine(): buf: QByteArray = self._process.readLine() line = os.fsdecode(buf.data()).rstrip("\n") # print("stdout:", repr(line)) self._process_stdout(line) def _on_ready_read_stderr(self) -> None: assert self._process is not None while self._process.canReadLine(): # print("stderr:", repr(line)) buf = self._process.readLine() line = os.fsdecode(buf.data()).rstrip("\n") self._process_stderr(line)
class HgClient(QObject): """ Class implementing the Mercurial command server interface. """ InputFormat = ">I" OutputFormat = ">cI" OutputFormatSize = struct.calcsize(OutputFormat) ReturnFormat = ">i" Channels = (b"I", b"L", b"o", b"e", b"r", b"d") def __init__(self, repoPath, encoding, vcs, parent=None): """ Constructor @param repoPath root directory of the repository (string) @param encoding encoding to be used by the command server (string) @param vcs reference to the VCS object (Hg) @param parent reference to the parent object (QObject) """ super(HgClient, self).__init__(parent) self.__server = None self.__started = False self.__version = None self.__encoding = vcs.getEncoding() self.__cancel = False self.__commandRunning = False self.__repoPath = repoPath # generate command line and environment self.__serverArgs = vcs.initCommand("serve") self.__serverArgs.append("--cmdserver") self.__serverArgs.append("pipe") self.__serverArgs.append("--config") self.__serverArgs.append("ui.interactive=True") if repoPath: self.__serverArgs.append("--repository") self.__serverArgs.append(repoPath) if encoding: self.__encoding = encoding if "--encoding" in self.__serverArgs: # use the defined encoding via the environment index = self.__serverArgs.index("--encoding") del self.__serverArgs[index:index + 2] def startServer(self): """ Public method to start the command server. @return tuple of flag indicating a successful start (boolean) and an error message (string) in case of failure """ self.__server = QProcess() self.__server.setWorkingDirectory(self.__repoPath) # connect signals self.__server.finished.connect(self.__serverFinished) prepareProcess(self.__server, self.__encoding) self.__server.start('hg', self.__serverArgs) serverStarted = self.__server.waitForStarted(5000) if not serverStarted: return False, self.tr( 'The process {0} could not be started. ' 'Ensure, that it is in the search path.' ).format('hg') self.__server.setReadChannel(QProcess.StandardOutput) ok, error = self.__readHello() self.__started = ok return ok, error def stopServer(self): """ Public method to stop the command server. """ if self.__server is not None: self.__server.closeWriteChannel() res = self.__server.waitForFinished(5000) if not res: self.__server.terminate() res = self.__server.waitForFinished(3000) if not res: self.__server.kill() self.__server.waitForFinished(3000) self.__started = False self.__server.deleteLater() self.__server = None def restartServer(self): """ Public method to restart the command server. @return tuple of flag indicating a successful start (boolean) and an error message (string) in case of failure """ self.stopServer() return self.startServer() def __readHello(self): """ Private method to read the hello message sent by the command server. @return tuple of flag indicating success (boolean) and an error message in case of failure (string) """ ch, msg = self.__readChannel() if not ch: return False, self.tr("Did not receive the 'hello' message.") elif ch != "o": return False, self.tr("Received data on unexpected channel.") msg = msg.split("\n") if not msg[0].startswith("capabilities: "): return False, self.tr( "Bad 'hello' message, expected 'capabilities: '" " but got '{0}'.").format(msg[0]) self.__capabilities = msg[0][len('capabilities: '):] if not self.__capabilities: return False, self.tr("'capabilities' message did not contain" " any capability.") self.__capabilities = set(self.__capabilities.split()) if "runcommand" not in self.__capabilities: return False, "'capabilities' did not contain 'runcommand'." if not msg[1].startswith("encoding: "): return False, self.tr( "Bad 'hello' message, expected 'encoding: '" " but got '{0}'.").format(msg[1]) encoding = msg[1][len('encoding: '):] if not encoding: return False, self.tr("'encoding' message did not contain" " any encoding.") self.__encoding = encoding return True, "" def __serverFinished(self, exitCode, exitStatus): """ Private slot connected to the finished signal. @param exitCode exit code of the process (integer) @param exitStatus exit status of the process (QProcess.ExitStatus) """ self.__started = False def __readChannel(self): """ Private method to read data from the command server. @return tuple of channel designator and channel data (string, integer or string or bytes) """ if self.__server.bytesAvailable() > 0 or \ self.__server.waitForReadyRead(10000): data = bytes(self.__server.peek(HgClient.OutputFormatSize)) if not data or len(data) < HgClient.OutputFormatSize: return "", "" channel, length = struct.unpack(HgClient.OutputFormat, data) channel = channel.decode(self.__encoding) if channel in "IL": self.__server.read(HgClient.OutputFormatSize) return channel, length else: if self.__server.bytesAvailable() < \ HgClient.OutputFormatSize + length: return "", "" self.__server.read(HgClient.OutputFormatSize) data = self.__server.read(length) if channel == "r": return (channel, data) else: return (channel, str(data, self.__encoding, "replace")) else: return "", "" def __writeDataBlock(self, data): """ Private slot to write some data to the command server. @param data data to be sent (string) """ if not isinstance(data, bytes): data = data.encode(self.__encoding) self.__server.write( QByteArray(struct.pack(HgClient.InputFormat, len(data)))) self.__server.write(QByteArray(data)) self.__server.waitForBytesWritten() def __runcommand(self, args, inputChannels, outputChannels): """ Private method to run a command in the server (low level). @param args list of arguments for the command (list of string) @param inputChannels dictionary of input channels. The dictionary must have the keys 'I' and 'L' and each entry must be a function receiving the number of bytes to write. @param outputChannels dictionary of output channels. The dictionary must have the keys 'o' and 'e' and each entry must be a function receiving the data. @return result code of the command, -1 if the command server wasn't started or -10, if the command was canceled (integer) @exception RuntimeError raised to indicate an unexpected command channel """ if not self.__started: return -1 self.__server.write(QByteArray(b'runcommand\n')) self.__writeDataBlock('\0'.join(args)) while True: QCoreApplication.processEvents() if self.__cancel: return -10 if self.__server is None: return -1 if self.__server is None or self.__server.bytesAvailable() == 0: QThread.msleep(50) continue channel, data = self.__readChannel() # input channels if channel in inputChannels: input = inputChannels[channel](data) if channel == "L": # echo the input to the output if it was a prompt outputChannels["o"](input) self.__writeDataBlock(input) # output channels elif channel in outputChannels: outputChannels[channel](data) # result channel, command is finished elif channel == "r": return struct.unpack(HgClient.ReturnFormat, data)[0] # unexpected but required channel elif channel.isupper(): raise RuntimeError( "Unexpected but required channel '{0}'.".format(channel)) # optional channels or no channel at all else: pass def __prompt(self, size, message): """ Private method to prompt the user for some input. @param size maximum length of the requested input (integer) @param message message sent by the server (string) @return data entered by the user (string) """ from .HgClientPromptDialog import HgClientPromptDialog input = "" dlg = HgClientPromptDialog(size, message) if dlg.exec_() == QDialog.Accepted: input = dlg.getInput() + '\n' return input def runcommand(self, args, prompt=None, input=None, output=None, error=None): """ Public method to execute a command via the command server. @param args list of arguments for the command (list of string) @keyparam prompt function to reply to prompts by the server. It receives the max number of bytes to return and the contents of the output channel received so far. @keyparam input function to reply to bulk data requests by the server. It receives the max number of bytes to return. @keyparam output function receiving the data from the server (string). If a prompt function is given, this parameter will be ignored. @keyparam error function receiving error messages from the server (string) @return output and errors of the command server (string). In case output and/or error functions were given, the respective return value will be an empty string. """ self.__commandRunning = True outputChannels = {} outputBuffer = None errorBuffer = None if prompt is not None or output is None: outputBuffer = io.StringIO() outputChannels["o"] = outputBuffer.write else: outputChannels["o"] = output if error: outputChannels["e"] = error else: errorBuffer = io.StringIO() outputChannels["e"] = errorBuffer.write inputChannels = {} if prompt is not None: def func(size): reply = prompt(size, outputBuffer.getvalue()) return reply inputChannels["L"] = func else: def myprompt(size): if outputBuffer is None: msg = self.tr("For message see output dialog.") else: msg = outputBuffer.getvalue() reply = self.__prompt(size, msg) return reply inputChannels["L"] = myprompt if input is not None: inputChannels["I"] = input self.__cancel = False self.__runcommand(args, inputChannels, outputChannels) if outputBuffer: out = outputBuffer.getvalue() else: out = "" if errorBuffer: err = errorBuffer.getvalue() else: err = "" self.__commandRunning = False return out, err def cancel(self): """ Public method to cancel the running command. """ self.__cancel = True self.restartServer() def wasCanceled(self): """ Public method to check, if the last command was canceled. @return flag indicating the cancel state (boolean) """ return self.__cancel def isExecuting(self): """ Public method to check, if the server is executing a command. @return flag indicating the execution of a command (boolean) """ return self.__commandRunning
class PluginProcessBase(QObject): proc_list = [] def __init__(self, wdir): QObject.__init__(self) PluginProcess.proc_list.append(self) self.is_rebuild = False self.is_query_fl = False self.sig = QuerySignal() self.proc = QProcess() self.proc.finished.connect(self._finished_cb) self.proc.error.connect(self._error_cb) self.proc.setWorkingDirectory(wdir) self.wdir = wdir def _cleanup(self): PluginProcess.proc_list.remove(self) if self.err_str != '': s = '<b>' + self.p_cmd + '</b><p>' + '<p>'.join( self.err_str.splitlines()) QMessageBox.warning(None, "Seascope", s, QMessageBox.Ok) if self.res != '': s = '<b>' + self.p_cmd + '</b><p>Summary<p>' + self.res QMessageBox.information(None, "Seascope", s, QMessageBox.Ok) def _error_cb(self, err): err_dict = { QProcess.FailedToStart: 'FailedToStart', QProcess.Crashed: 'Crashed', QProcess.Timedout: 'The last waitFor...() function timed out', QProcess.WriteError: 'An error occurred when attempting to write to the process', QProcess.ReadError: 'An error occurred when attempting to read from the process', QProcess.UnknownError: 'An unknown error occurred', } self.err_str = '<b>' + self.p_cmd + '</b><p>' + err_dict[err] self._cleanup() def _finished_cb(self, ret): res = self.proc.readAllStandardOutput().data().decode() self.err_str = self.proc.readAllStandardError().data().decode() #print 'output', res #print 'cmd:', self.p_cmd if self.is_rebuild: self.res = res self.sig.sig_rebuild.emit() elif self.is_query_fl: self.res = '' res = self.parse_query_fl(res) self.sig.sig_query_fl.emit(res) else: self.res = '' self.sig.sig_result_dbg.emit(self.p_cmd, res, self.err_str) try: res = self.parse_result(res, self.sig) except Exception as e: print(e) res = [[ '', '', '', 'error while parsing output of: ' + self.p_cmd ]] if res != None: self.sig.emit_result(res) self._cleanup() def run_query_process(self, pargs, sym, rquery=None): self.sig.sym = sym self.sig.rquery = rquery self.p_cmd = ' '.join(pargs) if os.getenv('SEASCOPE_DEBUG'): print(self.p_cmd) self.proc.start(pargs[0], pargs[1:]) if self.proc.waitForStarted() == False: return None self.proc.closeWriteChannel() return [self.sig.sig_result, self.sig.sig_result_dbg] def run_rebuild_process(self, pargs): self.is_rebuild = True self.p_cmd = ' '.join(pargs) self.proc.start(pargs[0], pargs[1:]) if self.proc.waitForStarted() == False: return None #print 'cmd:', pargs self.sig.sig_rebuild.connect(CtagsCache.flush) return self.sig.sig_rebuild def run_query_fl(self, pargs): self.is_query_fl = True self.p_cmd = ' '.join(pargs) self.proc.start(pargs[0], pargs[1:]) if self.proc.waitForStarted() == False: return None return self.sig.sig_query_fl def parse_query_fl(self, text): fl = [] for f in re.split('\r?\n', text.strip()): if f == '': continue fl.append(os.path.join(self.wdir, f)) return fl
class RarExtractor(Extractor): sig_entry_extracted = pyqtSignal(str, str) def __init__(self, filename: str, outdir: str) -> None: super().__init__() self._filename = os.path.abspath(filename) self._outdir = outdir self._process: Optional[QProcess] = None self._errors: List[str] = [] self._result: Optional[ExtractorResult] = None self._output_state = State.HEADER def interrupt(self): if self._process is not None: self._process.terminate() # self._process.kill() def extract(self) -> ExtractorResult: try: self._start_extract(self._outdir) self._process.waitForFinished(-1) return self._result except Exception as err: message = "{}: failure when extracting archive".format(self._filename) logger.exception(message) return ExtractorResult.failure(message) def _start_extract(self, outdir: str) -> None: # The directory is already created in ArchiveExtractor # os.mkdir(outdir) assert os.path.isdir(outdir) program = "rar" argv = ["x", "-p-", "-c-", self._filename] # "-w" + outdir has no effect logger.debug("RarExtractorWorker: launching %s %s", program, argv) self._process = QProcess() self._process.setProgram(program) self._process.setArguments(argv) self._process.setWorkingDirectory(outdir) self._process.start() self._process.closeWriteChannel() self._process.readyReadStandardOutput.connect(self._on_ready_read_stdout) self._process.readyReadStandardError.connect(self._on_ready_read_stderr) self._process.errorOccurred.connect(self._on_error_occured) self._process.finished.connect(self._on_process_finished) def _on_process_finished(self, exit_code, exit_status): self._process.setCurrentReadChannel(QProcess.StandardOutput) for line in os.fsdecode(self._process.readAll().data()).splitlines(): self._process_stdout(line) self._process.setCurrentReadChannel(QProcess.StandardError) for line in os.fsdecode(self._process.readAll().data()).splitlines(): self._process_stderr(line) if self._errors != []: message = "RAR: " + "\n".join(self._errors) else: message = "" if exit_status != QProcess.NormalExit or exit_code != 0: logger.error("RarExtractorWorker: something went wrong: %s %s", exit_code, exit_status) self._result = ExtractorResult.failure(message) else: logger.debug("RarExtractorWorker: finished successfully: %s %s", exit_code, exit_status) self._result = ExtractorResult.success(message) def _on_error_occured(self, error) -> None: logger.error("RarExtractorWorker: an error occured: %s", error) self._result = ExtractorResult.failure("RarExtractorWorker: an error occured: {}".format(error)) def _process_stdout(self, line): # print("stdout:", repr(line)) if self._output_state == State.HEADER: if BEGIN_RX.match(line): self._output_state = State.FILELIST elif self._output_state == State.FILELIST: m = FILE_RX.match(line) if m: entry = m.group(1).rstrip(" ") self.sig_entry_extracted.emit(entry, os.path.join(self._outdir, entry)) else: m = DIR_RX.match(line) if m: entry = m.group(1).rstrip(" ") self.sig_entry_extracted.emit(entry, os.path.join(self._outdir, entry)) elif line == "": pass # ignore empty line at the start else: # self._errors.append(line) # self._output_state = State.RESULT pass else: # self._errors.append(line) pass def _process_stderr(self, line): # print("stderr:", repr(line)) if line: self._errors.append(line) def _on_ready_read_stdout(self) -> None: assert self._process is not None self._process.setCurrentReadChannel(QProcess.StandardOutput) while self._process.canReadLine(): buf: QByteArray = self._process.readLine() line = os.fsdecode(buf.data()) line = line.rstrip("\n") self._process_stdout(line) def _on_ready_read_stderr(self) -> None: assert self._process is not None self._process.setCurrentReadChannel(QProcess.StandardError) while self._process.canReadLine(): buf: QByteArray = self._process.readLine() line = os.fsdecode(buf.data()) line = line.rstrip("\n") self._process_stderr(line)
class ProcessHandler(QObject): commandReceived = pyqtSignal(str) commandSent = pyqtSignal(str) processFinished = pyqtSignal() processError = pyqtSignal(str) def __init__(self, executable, parent=None): QObject.__init__(self, parent) self.executable = executable def run(self): self.process = QProcess() self.process.readyReadStandardOutput.connect(self.handleInput) self.process.setReadChannel(QProcess.StandardOutput) self.process.closeReadChannel(QProcess.StandardError) self.process.finished.connect(self.processFinished) self.pendingInput = QByteArray() self.in_message = False self.inputExpected = 0 self.pendingOutput = QByteArray() self.process.start(self.executable) # On Qt 5.6 and later, we can use the errorOccurred signal instead. if not self.process.waitForStarted(-1): self.processError.emit("The application quit unexpectedly.") def quit(self): print("Terminating %i" % self.process.processId()) self.process.closeReadChannel(QProcess.StandardOutput) self.process.closeReadChannel(QProcess.StandardError) self.process.closeWriteChannel() self.process.terminate() self.process.waitForFinished() self.thread().quit() def handleInput(self): self.pendingInput += self.process.readAllStandardOutput() try: while self.pendingInput.size() > 0: if not self.in_message: space = self.pendingInput.indexOf(b" ") if space == -1: return # Specify UTF-8 instead of falling back on something implicit. self.inputExpected = int( str(self.pendingInput.left(space), "utf8")) # Examine the rest of the input. self.pendingInput = self.pendingInput.mid(space + 1) self.in_message = True # Try to read the rest of the message. if len(self.pendingInput) >= self.inputExpected: command = self.pendingInput.left(self.inputExpected) self.pendingInput = self.pendingInput.mid( self.inputExpected) self.in_message = False self.inputExpected = 0 self.commandReceived.emit(str(command, "utf8")) elif self.process.bytesAvailable() > 0: self.pendingInput += self.process.readAllStandardOutput() else: return except ValueError: self.processError.emit(str(self.pendingInput, "utf8")) def handleOutput(self, message): # Write the length of the message and the message itself. message = "%i %s" % (len(message), message) self.pendingOutput += QByteArray(bytes(message, "utf8")) while self.pendingOutput.size() > 0: written = self.process.write(self.pendingOutput) if written == -1: self.processError.emit("Failed to write to application.") return # Handle the rest of the output. self.pendingOutput = self.pendingOutput.mid(written) self.commandSent.emit(message)
class Terminal(QWidget): errorSignal = pyqtSignal(str) outputSignal = pyqtSignal(str) def __init__(self, movable=False): super().__init__() self.setWindowFlags( Qt.Widget | Qt.WindowCloseButtonHint | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint ) self.movable = movable self.layout = QVBoxLayout() self.pressed = False self.process = QProcess(self) # self.editor = PlainTextEdit(self, self.movable) self.name = None # self.layout.addWidget(self.editor) self.process.readyReadStandardError.connect(self.onReadyReadStandardError) self.process.readyReadStandardOutput.connect(self.onReadyReadStandardOutput) self.setLayout(self.layout) self.setStyleSheet("QWidget {background-color:invisible;}") # self.showMaximized() # comment this if you want to embed this widget def ispressed(self): return self.pressed def center(self): qr = self.frameGeometry() cp = QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) def add(self): self.added() self.button = QPushButton("Hide terminal") self.button.setStyleSheet(""" height: 20; """) self.button.setFixedWidth(100) self.editor = PlainTextEdit(self, self.movable) self.highlighter = name_highlighter(self.editor.document(), str(getpass.getuser()), str(socket.gethostname()), str(os.getcwd())) self.layout.addWidget(self.button) self.layout.addWidget(self.editor) self.editor.commandSignal.connect(self.handle) self.button.clicked.connect(self.remove) self.editor.commandZPressed.connect(self.handle) def added(self): self.pressed = True def remove(self): self.editor.deleteLater() self.button.deleteLater() self.pressed = False def mousePressEvent(self, event): self.oldPos = event.globalPos() def mouseMoveEvent(self, event): delta = QPoint(event.globalPos() - self.oldPos) self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPos() def onReadyReadStandardError(self): self.error = self.process.readAllStandardError().data().decode() self.editor.appendPlainText(self.error.strip('\n')) self.errorSignal.emit(self.error) def onReadyReadStandardOutput(self): self.result = self.process.readAllStandardOutput().data().decode() self.editor.appendPlainText(self.result.strip('\n')) self.state = self.process.state() self.outputSignal.emit(self.result) def run(self, command): """Executes a system command.""" if self.process.state() != 2: self.process.start(command) print("Command executed, process state is now: " + str(self.process.state())) def handle(self, command): """Split a command into list so command echo hi would appear as ['echo', 'hi']""" real_command = command.replace(self.editor.name, "") if command == "True": if self.process.state() == 2: self.process.kill() self.editor.appendPlainText("Program execution killed, press enter") if real_command.startswith("python"): # TODO: start a python thread.... pass if real_command != "": command_list = real_command.split() else: command_list = None """Now we start implementing some commands""" if real_command == "clear": self.editor.clear() elif "&&" in command_list: pass # print(command_list) # print(command_list.index("&&")) # print(command_list[command_list.index("&&")+1:]) elif command_list is not None and command_list[0] == "echo": self.editor.appendPlainText(" ".join(command_list[1:])) elif real_command == "exit": qApp.exit() elif command_list is not None and command_list[0] == "cd" and len(command_list) > 1: try: os.chdir(" ".join(command_list[1:])) self.editor.name = "[" + str(getpass.getuser()) + "@" + str(socket.gethostname()) + "]" + " ~" + str( os.getcwd()) + " >$ " if self.highlighter: del self.highlighter self.highlighter = name_highlighter(self.editor.document(), str(getpass.getuser()), str(socket.gethostname()), str(os.getcwd())) except FileNotFoundError as E: self.editor.appendPlainText(str(E)) elif len(command_list) == 1 and command_list[0] == "cd": os.chdir(str(Path.home())) self.editor.name = "[" + str(getpass.getuser()) + "@" + str(socket.gethostname()) + "]" + " ~" + str( os.getcwd()) + " >$ " elif self.process.state() == 2: self.process.write(real_command.encode()) self.process.closeWriteChannel() elif command == self.editor.name + real_command: self.run(real_command) else: pass # When the user does a command like ls and then presses enter then it wont read the line where the cursor is on as a command