diff options
-rw-r--r-- | qutebrowser/browser/shared.py | 2 | ||||
-rw-r--r-- | qutebrowser/misc/guiprocess.py | 61 | ||||
-rwxr-xr-x | scripts/dev/run_vulture.py | 2 | ||||
-rw-r--r-- | tests/unit/misc/test_guiprocess.py | 4 |
4 files changed, 46 insertions, 23 deletions
diff --git a/qutebrowser/browser/shared.py b/qutebrowser/browser/shared.py index 41e7734f8..f2ff629f1 100644 --- a/qutebrowser/browser/shared.py +++ b/qutebrowser/browser/shared.py @@ -453,7 +453,7 @@ def _execute_fileselect_command( loop.exec() if tmpfilename is None: - selected_files = proc.final_stdout.splitlines() + selected_files = proc.stdout.splitlines() else: try: with open(tmpfilename, mode='r', encoding=sys.getfilesystemencoding()) as f: diff --git a/qutebrowser/misc/guiprocess.py b/qutebrowser/misc/guiprocess.py index 95bfd64af..5bcc83698 100644 --- a/qutebrowser/misc/guiprocess.py +++ b/qutebrowser/misc/guiprocess.py @@ -23,7 +23,7 @@ import locale import shlex from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, - QProcessEnvironment) + QProcessEnvironment, QByteArray) from qutebrowser.utils import message, log, utils from qutebrowser.browser import qutescheme @@ -61,16 +61,18 @@ class GUIProcess(QObject): self.cmd = None self.args = None - self.final_stdout: str = "" - self.final_stderr: str = "" + self.stdout: str = "" + self.stderr: str = "" self._proc = QProcess(self) + self._proc.setReadChannel(QProcess.StandardOutput) 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.readyRead.connect(self._on_ready_read) if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() @@ -78,6 +80,32 @@ class GUIProcess(QObject): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) + 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') + + @pyqtSlot() + def _on_ready_read(self): + if not self._output_messages: + return + + while True: + text = self._decode_data(self._proc.readLine()) + if not text: + break + + if '\r' in text: + # 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. + text = text.rsplit('\r', maxsplit=1)[-1] + self.stdout = self.stdout.rsplit('\n', maxsplit=1)[0] + '\n' + + self.stdout += text + + message.info(self.stdout.strip(), replace=True) + @pyqtSlot(QProcess.ProcessError) def _on_error(self, error): """Show a message if there was an error while spawning.""" @@ -113,17 +141,14 @@ class GUIProcess(QObject): 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') + self.stderr += self._decode_data(self._proc.readAllStandardError()) + self.stdout += self._decode_data(self._proc.readAllStandardOutput()) if self._output_messages: - if stdout: - message.info(stdout.strip()) - if stderr: - message.error(stderr.strip()) + if self.stdout: + message.info(self.stdout.strip(), replace=True) + if self.stderr: + message.error(self.stderr.strip()) if status == QProcess.CrashExit: exitinfo = "{} crashed.".format(self._what.capitalize()) @@ -141,14 +166,12 @@ class GUIProcess(QObject): "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()) + if self.stdout: + log.procs.error("Process stdout:\n" + self.stdout.strip()) + if self.stderr: + log.procs.error("Process stderr:\n" + self.stderr.strip()) - qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) - self.final_stdout = stdout - self.final_stderr = stderr + qutescheme.spawn_output = self._spawn_format(exitinfo, self.stdout, self.stderr) def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index 612b88637..d2b551689 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -61,7 +61,7 @@ def whitelist_generator(): # noqa: C901 yield 'scripts.utils.bg_colors' yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT' yield 'qutebrowser.misc.throttle.Throttle.set_delay' - yield 'qutebrowser.misc.guiprocess.GUIProcess.final_stderr' + yield 'qutebrowser.misc.guiprocess.GUIProcess.stderr' # Qt attributes yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl' diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py index 18e926fab..178ac3d12 100644 --- a/tests/unit/misc/test_guiprocess.py +++ b/tests/unit/misc/test_guiprocess.py @@ -127,11 +127,11 @@ def test_start_output_message(proc, qtbot, caplog, message_mock, py_proc, if stdout_msg is not None: assert stdout_msg.level == usertypes.MessageLevel.info assert stdout_msg.text == 'stdout text' - assert proc.final_stdout.strip() == "stdout text", proc.final_stdout + assert proc.stdout.strip() == "stdout text", proc.stdout if stderr_msg is not None: assert stderr_msg.level == usertypes.MessageLevel.error assert stderr_msg.text == 'stderr text' - assert proc.final_stderr.strip() == "stderr text", proc.final_stderr + assert proc.stderr.strip() == "stderr text", proc.stderr def test_start_env(monkeypatch, qtbot, py_proc): |