Reputation: 23
I am trying to add some features to a GUI i created some time ago, in particular the function I need is a text widget where the terminal commands I send show their output. The redirector class looks like this at the moment:
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def flush(self):
pass
and indeed it works. I replaced the os.system(...) command to open terminal commands with
a = subprocess.Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
and I read stdout through:b = a.stdout.read()
without a single problem (unfortunately i need that shell=True, otherwise some programs i need to call fail miserably).
After that I tried to have a realtime output on the tkinter text widget, so I changed b -->
while True:
b = a.stdout.readline().rstrip()
if not b:
break
print b
but it seems that the output appears only when the called process ends, i.e. a simple C software like
for(int i = 0; i<100000; i++){ cout << i << '\n';}
will print very slowly (I remark slowly given that a simple "ls" command will be printed line by line very slowly too) all the numbers at the end of the for cycle. Other than that I noticed that the GUI is frozen while the programs called through subprocess are run. Any ideas on how to solve these problems?
EDIT:
I created a simple terminal which runs commands using the multiprocessing class and Popen:
from Tkinter import *
from multiprocessing import Process, Pipe, Queue
import sys
from subprocess import PIPE, Popen, STDOUT
root = Tk()
root.title("Test Terminal")
root.resizable(False, False)
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def flush(self):
pass
terminal = Frame(root, bd=2, relief=GROOVE)
terminal.grid(row=0, sticky='NSEW')
TERM = Label(terminal, text='TERMINAL', font='Helvetica 16 bold')
TERM.grid(row=0, pady=10, sticky='NSEW')
termwid = Text(terminal, height=10)
termwid.grid(row=1, sticky='NSEW')
termwid.configure(state=DISABLED, font="Helvetica 12")
sys.stdout = StdRed(termwid)
enter = StringVar()
enter.set("")
termen = Entry(terminal, textvariable=enter)
queue = Queue(maxsize=1)
a = None
def termexe(execute):
a = Popen(execute, shell=True, stdout=PIPE, stderr=STDOUT)
while True:
line = a.stdout.readline().rstrip()
if not line:
break
else:
queue.put(line)
queue.put('')
def labterm(thi):
if queue.empty():
if thi != None:
if thi.is_alive():
root.after(0,lambda:labterm(thi))
else:
pass
else:
pass
else:
q = queue.get()
print q
root.after(0,lambda:labterm(thi))
def comter(event=None, exe=None, seq=None):
global enter
if seq == 1:
if exe != None:
th = Process(target=termexe, args=(exe,))
th.daemon = True
th.start()
labterm(th)
th.join()
else:
pass
else:
if exe != None:
th = Process(target=termexe, args=(exe,))
th.daemon = True
th.start()
labterm(th)
else:
th = Process(target=termexe, args=(enter.get(),))
th.daemon = True
th.start()
enter.set('')
labterm(th)
def resetterm():
global termwid
termwid.config(state=NORMAL)
termwid.delete(1.0, END)
termwid.config(state=DISABLED)
termen.bind('<Return>', comter)
resterm = Button(terminal, text="Clear", command=resetterm)
terbut = Button(terminal, text="Command", command=comter)
termen.grid(row=2, sticky='NSEW')
terbut.grid(row=3, sticky='NSEW')
resterm.grid(row=4, sticky='NSEW')
root.mainloop()
The problem is the acquisition is still not in real-time. Running from the entry in the software the program:
#include <iostream>
using namespace std;
int main()
{
int i = 0;
while(1)
{
cout << i << '\n';
i++;
int a = 0;
while(a < 10E6)
{
a++;
}
}
}
leads to nothing inside the text widget for a while and, after some time, the output appears suddenly. Any ideas on how to solve this problem?
Upvotes: 1
Views: 2531
Reputation: 23
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
Upvotes: 0
Reputation: 23
I tried using threads as suggested by @Pau B (switched to multiprocessing in the end), and I solved indeed the problem of the stuck GUI. The issue now is that running the program
for(int i = 0; i<100000; i++){ cout << i << '\n';}
doesn't return a realtime output, but it seems it is buffered and then appears after some time into the text widget. The code I'm using looks like this:
class StdRed(object):
def __init__(self, textwid):
self.text_space = textwid
def write(self, text):
self.text_space.config(state=NORMAL)
self.text_space.insert(END,text)
self.text_space.see(END)
self.text_space.update_idletasks()
self.text_space.config(state=DISABLED)
def termexe(execute):
a = Popen(execute, shell=True, stdout=PIPE, stderr=STDOUT)
while True:
line = a.stdout.readline().rstrip()
if not line:
break
else:
queue.put(line)
queue.put('')
def labterm():
global th
if queue.empty():
if th != None:
if th.is_alive():
root.after(0,labterm)
else:
pass
else:
pass
else:
q = queue.get()
print q
root.after(1,labterm)
def comter(event=None):
global enter
global th
if th != None:
if not th.is_alive():
th = Process(target=termexe, args=(enter.get(),))
th.start()
else:
pass
else:
th = Process(target=termexe, args=(enter.get(),))
th.start()
enter.set('')
labterm()
where comter() is called by a button or binded to 'Return' inside a text entry.
Upvotes: 0
Reputation: 534
The solution here is to use threading, otherwise the script wait till the job is done to make the GUI responsive again. With threading, your program will be running both the job and the GUI at the same time, an example of code:
import threading
def function():
pass
t = threading.Thread(target=function)
t.daemon = True # close pipe if GUI process exits
t.start()
I used this std redirector:
class StdRedirector():
"""Class that redirects the stdout and stderr to the GUI console"""
def __init__(self, text_widget):
self.text_space = text_widget
def write(self, string):
"""Updates the console widget with the stdout and stderr output"""
self.text_space.config(state=NORMAL)
self.text_space.insert("end", string)
self.text_space.see("end")
self.text_space.config(state=DISABLED)
Upvotes: 1