Display realtime output of a subprocess in a tkinter widget
Finally I found the solution.
After the window construction, you must add :
frame.pack()
# force drawing of the window
win.update_idletasks()
And then after every line insertion in the widget, you must also force a refresh with the same method only on the widget.# insert the line in the Text widget
t.insert(tk.END, out)
# force widget to display the end of the text (follow the input)
t.see(tk.END)
# force refresh of the widget to be sure that thing are displayed
t.update_idletasks()
How can I write output from a Subprocess to a text widget in tkinter in realtime?
welcome to stack overflow. I modified your code a bit (you had a ,
after defining start_button
and didnt import sys
, also i put your code below ##### Window Setting ####
into a boilerplate-code).
Your main issue was, that you do not make your Text
widget available in your run
and furthermore in your test
function (executed as a thread). So i handed over your widget as an argument to both functions (probably not the most pythonic way, however). For executing a command bound to a button i used from functools import partial
and binded the command including an argument via command=partial(run, textbox)
. Then i simply handed over the argument in run
to the thread with args=[textbox]
in the line where you create & start the thread. Finally, i updated the textbox with textbox.insert(tk.END, msg + "\n")
in your test
function while removing the print()
. The insert appends any text at the end to the textbox, the "\n"
starts a new line.
Here is the (slightly restructured) complete code (app.py):
import tkinter as tk
import subprocess
import threading
import sys
from functools import partial
# ### classes ####
class Redirect:
def __init__(self, widget, autoscroll=True):
self.widget = widget
self.autoscroll = autoscroll
def write(self, textbox):
self.widget.insert('end', textbox)
if self.autoscroll:
self.widget.see('end') # autoscroll
def flush(self):
pass
def run(textbox=None):
threading.Thread(target=test, args=[textbox]).start()
def test(textbox=None):
p = subprocess.Popen("python myprogram.py".split(), stdout=subprocess.PIPE, bufsize=1, text=True)
while p.poll() is None:
msg = p.stdout.readline().strip() # read a line from the process output
if msg:
textbox.insert(tk.END, msg + "\n")
if __name__ == "__main__":
fenster = tk.Tk()
fenster.title("My Program")
textbox = tk.Text(fenster)
textbox.grid()
scrollbar = tk.Scrollbar(fenster, orient=tk.VERTICAL)
scrollbar.grid()
textbox.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=textbox.yview)
start_button = tk.Button(fenster, text="Start", command=partial(run, textbox))
start_button.grid()
old_stdout = sys.stdout
sys.stdout = Redirect(textbox)
fenster.mainloop()
sys.stdout = old_stdout
And here is the code of the test-file myprogram.py i created:import time
for i in range(10):
print(f"TEST{i}")
time.sleep(1)
Widget to Display subprocess stdout?
You may want to use the Tkinter.Text
widget, described here.
The description is quite long, but what you need to understand is mainly that this widget can be used as a buffer, as text should be inserted inside.
So, for each new line in the subprocess
output, you will have to insert text where you want it. Example:
t = Tkinter.Text(root)
while some_condition:
s = p.readline() # get subprocess output
t.insert(END, s)
EDIT Have a look here for getting subprocess output line by line.You may also want to have a look to Tkinter.Scrollbar
and Text.see()
to tune the display.
Python 3 - Getting realtime output of cmd in Tkinter
You can solve this in two ways, first better for YT is to use pafy library, base of pafy is ytdl so it can be done since i had made that but it was slow, and i got another idea that i'am developing now, and with little modification you can connect pafys output with tkinter wodget.Second solution is
def sudo(self, cmnd, terminal, top): # 1
sudo_password = 'your sudo code' + '\n'
sudos = ['sudo', '-S']
terminal.delete('1.0', END)
for item in eval(cmnd):
cmd = sudos + item.split()
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, universal_newlines=True)
p.stdin.write(sudo_password)
p.poll()
while True:
line = p.stdout.readline()
terminal.insert(END, line)
terminal.see(END)
top.updates()
if not line and p.poll is not None: break
while True:
err = p.stderr.readline()
terminal.insert(END, err)
terminal.see(END)
top.updates()
if not err and p.poll is not None: break
terminal.insert(END, '\n * END OF PROCESS *')
cmnd - list of commands you want to execute, ['youtube-dl some link'], with even one command it should be LIST
terminal - thats Text widget in my app, but you can use any wiget as well, only you would have to change all lines terminal.insert(END, 'some text') to terminal.insert(0, 'some text') - END to 0
top is scrollbar container for my app which you can remove if you don't need it
of course you have to provide root=Tk(), parents and other containers for the terminal widget .
TKinter GUI freezes until subprocess ends and realtime output to text Widget
Maybe this could help others, I solved the issue replacing '\n' with endl. It seems cout in the while loop is buffered and the stdout flush is called only after a while, while with endl the function is called after every cycle
Issues intercepting subprocess output in real time
The problem here is that process.stdout.readline()
will block until a full line is available. This means the condition line == ''
will never be met until the process exits. You have two options around this.
First you can set stdout to non-blocking and manage a buffer yourself. It would look something like this. EDIT: As Terry Jan Reedy pointed out this is a Unix only solution. The second alternative should be preferred.
import fcntl
...
def startProcess(self):
self.process = subprocess.Popen(['./subtest.sh'],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE,
bufsize=0) # prevent any unnecessary buffering
# set stdout to non-blocking
fd = self.process.stdout.fileno()
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
# schedule updatelines
self.after(100, self.updateLines)
def updateLines(self):
# read stdout as much as we can
line = ''
while True:
buff = self.process.stdout.read(1024)
if buff:
buff += line.decode()
else:
break
self.console.config(state=tkinter.NORMAL)
self.console.insert(tkinter.END, line)
self.console.config(state=tkinter.DISABLED)
# schedule callback
if self.process.poll() is None:
self.after(100, self.updateLines)
The second alternative is to have a separate thread read the lines into a queue. Then have updatelines pop from the queue. It would look something like thisfrom threading import Thread
from queue import Queue, Empty
def readlines(process, queue):
while process.poll() is None:
queue.put(process.stdout.readline())
...
def startProcess(self):
self.process = subprocess.Popen(['./subtest.sh'],
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
self.queue = Queue()
self.thread = Thread(target=readlines, args=(self.process, self.queue))
self.thread.start()
self.after(100, self.updateLines)
def updateLines(self):
try:
line = self.queue.get(False) # False for non-blocking, raises Empty if empty
self.console.config(state=tkinter.NORMAL)
self.console.insert(tkinter.END, line)
self.console.config(state=tkinter.DISABLED)
except Empty:
pass
if self.process.poll() is None:
self.after(100, self.updateLines)
The threading route is probably safer. I'm not positive that setting stdout to non-blocking will work on all platforms.
Related Topics
Stop Matplotlib Repeating Labels in Legend
Running Multiple Bash Commands with Subprocess
Reading Tar File Contents Without Untarring It, in Python Script
Basic Program to Convert Integer to Roman Numerals
How to Exit from Python Without Traceback
Does Python Evaluate If's Conditions Lazily
Extract Int from String in Pandas
How Does Sklearn.Svm.Svc's Function Predict_Proba() Work Internally
How to Get the Version Defined in Setup.Py (Setuptools) in My Package
How to Use Asyncio with Existing Blocking Library
How to Convert an Int to a Hex String
Opencv Python: Draw Minarearect ( Rotatedrect Not Implemented)
Filename and Line Number of Python Script
Difference Between Variable and Get_Variable in Tensorflow
Numpy 1.21.2 May Not Yet Support Python 3.10
Spark Dataframe: Computing Row-Wise Mean (Or Any Aggregate Operation)