Embed interactive console?
-
wrote on 25 Aug 2021, 00:25 last edited by lumbo7332
I'm trying to run a function that prints to STDOUT and requires user interaction, within a widget. So far I have a class called
EmbeddedTerminal
, and that is a subclass ofQTextEdit
. Then it has a function calledrun_func
that takes a function name, and*args
and**kwargs
. Then it runs that function, redirects the output, and sets theQTextEdit
to the output of the function. Problem is, that only works for a function that isn't interactive. I figure I have to do something with threading.This is what I have so far, but it's not setting the
QTextEdit
from contextlib import redirect_stdout from io import StringIO from PyQt5.QtWidgets import QTextEdit from PyQt5.QtCore import QObject, QThread, pyqtSignal class EmbeddedTerminal(QTextEdit): def __init__(self, parent): super(EmbeddedTerminal, self).__init__(parent) def run_func(self, func, *args, **kwargs): self.thread = QThread() self.worker = TerminalWorker(func, args, kwargs) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.run) self.worker.progress.connect(self.update_terminal) def update_terminal(self, text): self.setText(text) class TerminalWorker(QObject): finished = pyqtSignal() progress = pyqtSignal(str) def __init__(self, func, args, kwargs): QObject.__init__(self) self.func = func self.args = args self.kwargs = kwargs def run(self): with redirect_stdout(StringIO()) as f: self.func(self.args, self.kwargs) output = f.getvalue() self.progress.emit(output)
-
I'm trying to run a function that prints to STDOUT and requires user interaction, within a widget. So far I have a class called
EmbeddedTerminal
, and that is a subclass ofQTextEdit
. Then it has a function calledrun_func
that takes a function name, and*args
and**kwargs
. Then it runs that function, redirects the output, and sets theQTextEdit
to the output of the function. Problem is, that only works for a function that isn't interactive. I figure I have to do something with threading.This is what I have so far, but it's not setting the
QTextEdit
from contextlib import redirect_stdout from io import StringIO from PyQt5.QtWidgets import QTextEdit from PyQt5.QtCore import QObject, QThread, pyqtSignal class EmbeddedTerminal(QTextEdit): def __init__(self, parent): super(EmbeddedTerminal, self).__init__(parent) def run_func(self, func, *args, **kwargs): self.thread = QThread() self.worker = TerminalWorker(func, args, kwargs) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.run) self.worker.progress.connect(self.update_terminal) def update_terminal(self, text): self.setText(text) class TerminalWorker(QObject): finished = pyqtSignal() progress = pyqtSignal(str) def __init__(self, func, args, kwargs): QObject.__init__(self) self.func = func self.args = args self.kwargs = kwargs def run(self): with redirect_stdout(StringIO()) as f: self.func(self.args, self.kwargs) output = f.getvalue() self.progress.emit(output)
wrote on 25 Aug 2021, 00:47 last edited by eyllanesc@lumbo7332 what is interactive function? please provide a minimal and verifiable example.
Note: You should not modify (or access) GUI elements and directly related elements (such as models associated with views) from another thread as they are not thread-safe. That has nothing to do with the stdout redirect but with a rule set by Qt.
-
@lumbo7332 what is interactive function? please provide a minimal and verifiable example.
Note: You should not modify (or access) GUI elements and directly related elements (such as models associated with views) from another thread as they are not thread-safe. That has nothing to do with the stdout redirect but with a rule set by Qt.
wrote on 25 Aug 2021, 01:04 last edited by@eyllanesc I mean something that uses the
input()
function. Although, my current implementation with the worker doesn't seem to set theQTextEdit
, no matter what function I use (e.g.print
). Here's a minimal example:
https://cloud.haddock.cc/s/3792TsJqoPnRetK -
@eyllanesc I mean something that uses the
input()
function. Although, my current implementation with the worker doesn't seem to set theQTextEdit
, no matter what function I use (e.g.print
). Here's a minimal example:
https://cloud.haddock.cc/s/3792TsJqoPnRetKwrote on 25 Aug 2021, 01:21 last edited by@lumbo7332 I do not know if it is an oversight but anyway your code has 2 errors.
- You never start the QThread.
- You must use
self.func(*self.args, **self.kwargs)
.
from contextlib import redirect_stdout from io import StringIO from PyQt5.QtWidgets import QTextEdit from PyQt5.QtCore import QObject, QThread, pyqtSignal class EmbeddedTerminal(QTextEdit): def __init__(self, parent): super(EmbeddedTerminal, self).__init__(parent) def run_func(self, func, *args, **kwargs): self.thread = QThread() self.worker = TerminalWorker(func, args, kwargs) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.run) self.worker.progress.connect(self.update_terminal) self.thread.start() def update_terminal(self, text): self.setText(text) class TerminalWorker(QObject): finished = pyqtSignal() progress = pyqtSignal(str) def __init__(self, func, args, kwargs): QObject.__init__(self) self.func = func self.args = args self.kwargs = kwargs def run(self): with redirect_stdout(StringIO()) as f: self.func(*self.args, **self.kwargs) output = f.getvalue() self.progress.emit(output)
-
@lumbo7332 I do not know if it is an oversight but anyway your code has 2 errors.
- You never start the QThread.
- You must use
self.func(*self.args, **self.kwargs)
.
from contextlib import redirect_stdout from io import StringIO from PyQt5.QtWidgets import QTextEdit from PyQt5.QtCore import QObject, QThread, pyqtSignal class EmbeddedTerminal(QTextEdit): def __init__(self, parent): super(EmbeddedTerminal, self).__init__(parent) def run_func(self, func, *args, **kwargs): self.thread = QThread() self.worker = TerminalWorker(func, args, kwargs) self.worker.moveToThread(self.thread) self.thread.started.connect(self.worker.run) self.worker.progress.connect(self.update_terminal) self.thread.start() def update_terminal(self, text): self.setText(text) class TerminalWorker(QObject): finished = pyqtSignal() progress = pyqtSignal(str) def __init__(self, func, args, kwargs): QObject.__init__(self) self.func = func self.args = args self.kwargs = kwargs def run(self): with redirect_stdout(StringIO()) as f: self.func(*self.args, **self.kwargs) output = f.getvalue() self.progress.emit(output)
wrote on 25 Aug 2021, 01:27 last edited by@eyllanesc Thank you. That helps with
print
working. But the input prompt still isn't showing up. -
@eyllanesc Thank you. That helps with
print
working. But the input prompt still isn't showing up.wrote on 25 Aug 2021, 01:34 last edited by eyllanesc@lumbo7332 What is the purpose of redirect_stdout? That all the printing in the console is shown in the QTextEdit, because that is what happens.
input("test")
prints "test" in stdout and then waits for the input, because now stdout is redirected to QTextEdit and not to the console.Do you want the "test" to be printed in the console and in the QTextEdit at the same time?
-
wrote on 25 Aug 2021, 01:38 last edited by lumbo7332
@eyllanesc I was trying to make it so the prompt gets printed in the
QTextEdit
, I can type input in theQTextEdit
, and thatinput()
will get that. -
@eyllanesc I was trying to make it so the prompt gets printed in the
QTextEdit
, I can type input in theQTextEdit
, and thatinput()
will get that.wrote on 25 Aug 2021, 01:44 last edited by eyllanesc@lumbo7332
No, it doesn't work like that. in input the message is printed in sys.stdout and the information is obtained from sys.stdin. What you write to QTextEdit will not write to sys.stdin. A possible solution would be that you detect when the user writes in the QTextEdit and that information write in sys.stdin.You could also take inspiration from my answers:
-
@lumbo7332
No, it doesn't work like that. in input the message is printed in sys.stdout and the information is obtained from sys.stdin. What you write to QTextEdit will not write to sys.stdin. A possible solution would be that you detect when the user writes in the QTextEdit and that information write in sys.stdin.You could also take inspiration from my answers:
wrote on 25 Aug 2021, 03:06 last edited byThis post is deleted! -
@lumbo7332
No, it doesn't work like that. in input the message is printed in sys.stdout and the information is obtained from sys.stdin. What you write to QTextEdit will not write to sys.stdin. A possible solution would be that you detect when the user writes in the QTextEdit and that information write in sys.stdin.You could also take inspiration from my answers:
wrote on 25 Aug 2021, 18:34 last edited byThis post is deleted! -
@lumbo7332
No, it doesn't work like that. in input the message is printed in sys.stdout and the information is obtained from sys.stdin. What you write to QTextEdit will not write to sys.stdin. A possible solution would be that you detect when the user writes in the QTextEdit and that information write in sys.stdin.You could also take inspiration from my answers:
wrote on 25 Aug 2021, 21:21 last edited by lumbo7332@eyllanesc I currently just want it to set the text of
QTextEdit
tosys.stdout
, I can handle the input later. I'm really not sure why this isn't capturing the output , I have it setup to emit the output, but theQTextEdit
isn't changing.class TerminalWorker(QObject): finished = pyqtSignal() outputChanged = pyqtSignal(str) def __init__(self, func, args, kwargs): QObject.__init__(self) self.func = func self.args = args self.kwargs = kwargs def run(self): with redirect_stdout(StringIO()) as f: self.func(*self.args, **self.kwargs) print(f) self.outputChanged.emit(f.getvalue())
Minimal example: https://github.com/lumbo7332/qt-terminal
1/11