I have a main window (PyQt5), that starts other "script.py" as subprocess in QtThread (script.py also has a window PyQt5). The thread in main program should send some data in while-True-cycle, and my script also should show me in his window the data, that right now send the thread.
Now its like:
main.py:
class Send(QtCore.QThread):
def run(self) -> None:
self.proc = subprocess.Popen(['python3', 'pipeFile.py'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True, encoding='utf-8')
for i in range(5):
print('sended')
self.proc.write('some data')
time.sleep(1)
script.py
class Check(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def run(self) -> None:
while True:
print('read')
print(sys.stdin.read())
I tried search info in different websites, forums, I read documentations, but I don't understand it well. How i can do this IPC with a PIPE?
Sorry for my bad English, I'm bad at learning languages, but I'm trying ;) It would be nice if you correct me
So, it was so simple... I just had to add flush() to clear the buffer
I hope my question helps someone
main.py
class Send(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.proc = None
def run(self) -> None:
# time.sleep(1)
self.proc = subprocess.Popen(['python3', 'pipeFile.py'], shell=False, stdin=subprocess.PIPE, text=True, encoding='utf-8')
for i in range(5):
self.proc.stdin.write('lol\n')
print('sended')
self.proc.stdin.flush()
script.py
class Check(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def run(self) -> None:
while True:
s = sys.stdin.readline()
if s != '':
print('read')
print(s)
sys.stdin.flush()
Related
I made two tkinter text boxes one of which takes your python script as input and the other one shows results of the execution of your script but when I used input() command I got an error. Below given is the class for stdout redirector and also the execute function which executes after reading the script, which works fine. I have not included Text, tkinter, etc because I use all the general methods that work with the code like Text.get(), Text.mark_set(), Text.replace(), etc and also some of the functions are not included here. Other than the script and the output boxes I also tried to embed whole of the console in a textbox with InteractiveConsole but the problem was same in the case of receiving input or stdin but in both the cases stdout and stderr works fine.
from code import InteractiveConsole, InteractiveInterpreter
class StdoutRedirector(object):
def __init__(self, text_widget):
self.text_space = text_widget
def write(self, string):
self.text_space.insert('end', string)
self.text_space.see('end')
##class StdinRedirector(object):
## def __init__(self, text_widget):
## self.text_space = text_widget
##
## def readline(self) -> str:
## t = self.text_space.get(INSERT, f"{int(text.index(INSERT).split('.')[0])}.{int(text.index(INSERT).split('.')[1])}")
## return t
def execute(event=None):
save()
code = text.get('1.0',END+'-1c')
stdin = sys.stdin
stdout = sys.stdout
stderr = sys.stderr
output.delete('1.0',END)
## def a():
## sys.stdin = StdinRedirector(output)
## output.bind('<Return>', lambda: a)
sys.stdout = StdoutRedirector(output)
sys.stderr = StdoutRedirector(output)
interp = InteractiveInterpreter()
interp.runcode(code)
sys.stdout = stdout
sys.stderr = stderr
## sys.stdin = stdin
After which I tried Redirecting stdin, which obviously didn't work, and instead the application hung and the window stopped responding even after trying again and again.
Please help me with this... I don't know if its impossible but PyCharm and others have I/O Streams inside them so maybe the console or the execution window CAN be wholly embedded in a text box.
Ok so after researching on the web, in docs, and inside the code of the queue, idlelib and subprocess modules, I figured out the simplest way to make tkinter Textbox interact with python console as stdin, stdout, and stderr receiver. Here's the code:
import tkinter as tk
import subprocess
import queue
import os
from threading import Thread
class Console(tk.Frame):
def __init__(self, parent=None, **kwargs):
tk.Frame.__init__(self, parent, **kwargs)
self.parent = parent
# create widgets
self.ttytext = tk.Text(self, wrap=tk.WORD)
self.ttytext.pack(fill=tk.BOTH, expand=True)
self.ttytext.linenumbers.pack_forget()
self.p = subprocess.Popen(["jupyter", "qtconsole"], stdout=subprocess.PIPE, stdin=subprocess.PIPE,
stderr=subprocess.PIPE, creationflags=subprocess.CREATE_NO_WINDOW)
# make queues for keeping stdout and stderr whilst it is transferred between threads
self.outQueue = queue.Queue()
self.errQueue = queue.Queue()
# keep track of where any line that is submitted starts
self.line_start = 0
# a daemon to keep track of the threads so they can stop running
self.alive = True
# start the functions that get stdout and stderr in separate threads
Thread(target=self.readfromproccessout).start()
Thread(target=self.readfromproccesserr).start()
# start the write loop in the main thread
self.writeloop()
# key bindings for events
self.ttytext.bind("<Return>", self.enter)
self.ttytext.bind('<BackSpace>', self.on_bkspace)
self.ttytext.bind('<Delete>', self.on_delete)
self.ttytext.bind('<<Copy>>', self.on_copy)
self.ttytext.bind('<<Paste>>', self.on_paste)
self.ttytext.bind('<Control-c>', self.on_copy)
self.ttytext.bind('<Control-v>', self.on_paste)
def destroy(self):
"""This is the function that is automatically called when the widget is destroyed."""
self.alive = False
# write exit() to the console in order to stop it running
self.p.stdin.write("exit()\n".encode())
self.p.stdin.flush()
# call the destroy methods to properly destroy widgets
self.ttytext.destroy()
tk.Frame.destroy(self)
def enter(self, event):
"""The <Return> key press handler"""
cur_ind = str(self.ttytext.index(tk.INSERT))
if int(cur_ind.split('.')[0]) < int(self.ttytext.search(': ', tk.END, backwards=True).split('.')[0]):
try:
selected = self.ttytext.get('sel.first', 'sel.last')
if len(selected) > 0:
self.ttytext.insert(tk.END, selected)
self.ttytext.mark_set(tk.INSERT, tk.END)
self.ttytext.see(tk.INSERT)
return 'break'
except:
selected = self.ttytext.get(
self.ttytext.search(': ', tk.INSERT, backwards=True), tk.INSERT)
self.ttytext.insert(tk.END, selected.strip(': '))
self.ttytext.mark_set(tk.INSERT, tk.END)
self.ttytext.see(tk.INSERT)
return 'break'
string = self.ttytext.get(1.0, tk.END)[self.line_start:]
self.line_start += len(string)
self.p.stdin.write(string.encode())
self.p.stdin.flush()
def on_bkspace(self, event):
pass
def on_delete(self, event):
pass
def on_key(self, event):
"""The typing control (<KeyRelease>) handler"""
cur_ind = str(self.ttytext.index(tk.INSERT))
try:
if int(cur_ind.split('.')[0]) < int(self.ttytext.search(r'In [0-9]?', tk.END, backwards=True).split('.')[0]):
return 'break'
except:
return
def on_copy(self, event):
"""<Copy> event handler"""
self.ttytext.clipboard_append(self.ttytext.get('sel.first', 'sel.last'))
# I created this function because I was going to make a custom textbox
def on_paste(self, event):
"""<Paste> event handler"""
self.ttytext.insert(tk.INSERT, self.ttytext.clipboard_get())
# I created this function because I was going to make a custom textbox
def readfromproccessout(self):
"""To be executed in a separate thread to make read non-blocking"""
while self.alive:
data = self.p.stdout.raw.read(1024).decode()
self.outQueue.put(data)
def readfromproccesserr(self):
"""To be executed in a separate thread to make read non-blocking"""
while self.alive:
data = self.p.stderr.raw.read(1024).decode()
self.errQueue.put(data)
def writeloop(self):
"""Used to write data from stdout and stderr to the Text widget"""
# if there is anything to write from stdout or stderr, then write it
if not self.errQueue.empty():
self.write(self.errQueue.get())
if not self.outQueue.empty():
self.write(self.outQueue.get())
# run this method again after 10ms
if self.alive:
self.after(10, self.writeloop)
def write(self, string):
self.ttytext.insert(tk.END, string)
self.ttytext.see(tk.END)
self.line_start += len(string)
self.ttytext.inst_trigger()
if __name__ == '__main__':
root = tk.Tk()
main_window = Console(root)
main_window.pack(fill=tk.BOTH, expand=True)
main_window.ttytext.focus_force()
root.mainloop()
The code above uses jupyter qtconsole (because it is very handy), otherwise simple python shell can also be used using the InteractiveShell() in code module.
I have not completely made functions for Enter key, Up and Down arrow keys. These can be made by the user as per their choice.
This can also be found in Oli's answer here and it is customizable.
I am writing a python app to take some inputs from the user and call a shell script based on these inputs.
This shell script can run for quite sometime, and I want to redirect the output it produces (in realtime) to tkinter.
I managed to do that, but it only happens after the shell script is completely finished, not as soon as the shell script "echos" something for example.
So main problem:
1. Output appears in the text widget only after shellScript.sh is exited (although if I run the same in the terminal manually, I see continuous output).
2. Side problem: all the "\n" printed in the output are just printed as "\n" and no new lines are added in the text widget.
here is what I have:
class RedirectText(object):
def __init__(self, text_ctrl):
"""Constructor"""
self.output = text_ctrl
def write(self, string):
self.output.insert(tk.END, string[2:])
class Gui(GuiApp):
def __init__(self, master=None):
redir = RedirectText(self.text_Output)
sys.stdout = redir
def testRun(self, event=None):
p = subprocess.Popen(["./shellScript.sh"], stdout=subprocess.PIPE)
print(p.communicate()[0])
As p.communicate() will wait for the process to complete, so use p.poll() and p.stdout.readline() instead. Also put the process in a thread to avoid blocking the main thread:
import threading
...
class Gui(GuiApp):
...
def runScript(self):
print('started')
p = subprocess.Popen(['./shellScript.sh'], stdout=subprocess.PIPE, bufsize=1, text=True)
while p.poll() is None: # check whether process is still running
msg = p.stdout.readline().strip() # read a line from the process output
if msg:
print(msg)
print('finished')
def testRun(self, event=None):
# create a thread to run the script
threading.Thread(target=self.runScript, daemon=True).start()
Segmentation Fault in subprocess.stdout while displaying on QPlainTextEdit
Hi,
I am starting a thread to the function shown and streaming its result to a QTextEdit object in the thread. The function sometimes crashes with a segmentation fault for unknown reasons.
self.plainTextEdit = QPlainTextEdit()
self.thread = Thread(target = runcmd, args = ("make dc",))
self.thread.start()
self.thread.join()
def runcmd(self,cmd):
process = subprocess.Popen(shlex.split(cmd),stdout=subprocess.PIPE, bufsize=-1)
while True:
line = process.stdout.readline()
if not line:
break
self.plainTextEdit.moveCursor(QTextCursor.End)
self.plainTextEdit.insertPlainText(line.strip())
process.terminate()
The
make dc
command is a call to the design compiler synthesis tool. If I try to print the
line
variable instead of writing to the plainTextEdit Object the thread runs fine, displaying the result in the terminal window. Any help/advice is welcome......
Thank You
You can't use QT things from a python thread. Your options:
Send the data back from the thread to your main program, using a queue.Queue or some other similar synchronization object. Then the main program adds the data to the text widget. Documentation here.
Use a QThread, which is the QT equivalent of threads. Documentation here.
You cannot update the Qt GUI from another thread. Fortunately for us, Qt gave us signals, and PyQt gave us pyqtSignal, just for this situation. There are a few ways to do it, but I prefer the following style.
class YourClass(QtCore.QObject):
appendSignal = pyqtSignal(str)
def __init__(self):
super(YourClass, self).__init__()
self.plainTextEdit = QPlainTextEdit()
self.appendSignal.connect(self.reallyAppendToTextEdit)
self.appendToTextEdit = self.appendSignal.emit
self.thread = Thread(target = runcmd, args = ("make dc",))
self.thread.start()
self.thread.join()
def reallyAppendToTextEdit(self, txt):
self.plainTextEdit.moveCursor(QTextCursor.End)
self.plainTextEdit.insertPlainText(txt)
def runcmd(self,cmd):
process = subprocess.Popen(shlex.split(cmd),stdout=subprocess.PIPE, bufsize=-1)
while True:
line = process.stdout.readline()
if not line:
break
self.appendToTextEdit(line.strip())
process.terminate()
Instead of using subprocess.Popen() you should use QProcess which is GUI friendly so it is not necessary to use a new thread.
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import shlex
class LogWidget(QWidget):
def __init__(self, parent=None):
super(LogWidget, self).__init__(parent)
lay = QVBoxLayout(self)
self.plainTextEdit = QPlainTextEdit()
lay.addWidget(self.plainTextEdit)
self.runcmd("make dc")
def runcmd(self, cmd):
process = QProcess(self)
process.readyReadStandardOutput.connect(self.onReadyReadStandardOutput)
process.readyReadStandardError.connect(self.onReadyReadStandardError)
program, *arguments = shlex.split(cmd)
process.start(program, arguments)
def onReadyReadStandardOutput(self):
process = self.sender()
self.plainTextEdit.appendPlainText(str(process.readAllStandardOutput()))
def onReadyReadStandardError(self):
process = self.sender()
self.plainTextEdit.appendPlainText(str(process.readAllStandardError()))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = LogWidget()
w.show()
sys.exit(app.exec_())
I've spent about 6 hours on stack overflow, rewriting my python code and trying to get this to work. It just doesn't tho. No matter what I do.
The goal:
Getting the output of a subprocess to appear in real time in a tkinter text box.
The issue:
I can't figure out how to make the Popen work in real time. It seems to hang until the process is complete. (Run on its own, the process works completely as expected, so it's just this thing that has the error)
Relevant code:
import os
import tkinter
import tkinter.ttk as tk
import subprocess
class Application (tk.Frame):
process = 0
def __init__ (self, master=None):
tk.Frame.__init__(self, master)
self.grid()
self.createWidgets()
def createWidgets (self):
self.quitButton = tk.Button(self, text='Quit', command=self.quit)
self.quitButton.grid()
self.console = tkinter.Text(self)
self.console.config(state=tkinter.DISABLED)
self.console.grid()
def startProcess (self):
dir = "C:/folder/"
self.process = subprocess.Popen([ "python", "-u", dir + "start.py" ], stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE, cwd=dir)
self.updateLines()
def updateLines (self):
self.console.config(state=tkinter.NORMAL)
while True:
line = self.process.stdout.readline().decode().rstrip()
if line == '' and self.process.poll() != None:
break
else:
self.console.insert(tkinter.END, line + "\n")
self.console.config(state=tkinter.DISABLED)
self.after(1, self.updateLines)
app = Application()
app.startProcess()
app.mainloop()
Also, feel free to destroy my code if it's written poorly. This is my first python project, I don't expect to be any good at the language yet.
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 this
from 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.
I'm trying to create a basic PyGTK app to embed MPlayer inside a window (since it otherwise doesn't work well with tiling WM's, which I like).
I'll put my code so far at the end of this post, but basically my setup currently involves a Window containing a DrawingArea which I embed MPlayer into using the `-wid' command-line option.
The problem I'm having is that, when resizing, I get the following sorts of visual artifacts (see inside the red box):
I've tried calling queue_draw() on the DrawingArea when a configure-event happens, but this seems to have no effect. Anyone have any ideas?
My complete code is as follows: (command-line usage is `$0 [ vid ]')
#!/usr/bin/env python2
import sys
import os
import subprocess
import time
import string
import gtk
import gobject
import pygtk
pygtk.require('2.0')
class MPlayer:
def __init__(self, path, draw, show_output=True):
self.path = path
self.draw = draw
self.fifo = "/tmp/%s.%d" % (os.path.basename(__file__), time.time())
# Start mplayer in draw
cmd = string.split("mplayer -slave -wid %d -input file=%s" % \
(self.draw.window.xid, self.fifo))
cmd.append(self.path)
if show_output:
process = subprocess.Popen(cmd)
else:
self.devnull = open(os.devnull)
process = subprocess.Popen(cmd, stdout=self.devnull, \
stderr=self.devnull)
self.pid = process.pid
def __enter__(self):
os.mkfifo(self.fifo)
return self
def __exit__(self, ext_type, exc_value, traceback):
if hasattr(self, "devnull"):
self.devnull.close()
os.unlink(self.fifo)
# Send cmd to mplayer via fifo
def exe(self, cmd, *args):
if not self.pid: return
full_cmd = "%s %s\n" % (cmd, string.join([str(arg) for arg in args]))
with open(self.fifo, "w+") as fifo:
fifo.write(full_cmd)
fifo.flush()
class MPlayerWrapper:
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.draw = gtk.DrawingArea()
self.mplayer = None
self.setup_widgets()
def setup_widgets(self):
self.window.connect("destroy", gtk.main_quit)
self.window.connect("key_press_event", self.key_press_event)
self.draw.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
self.draw.connect("configure_event", self.redraw)
self.window.add(self.draw)
self.window.show_all()
def mplayer_exe(self, cmd, *args):
if self.mplayer:
self.mplayer.exe(cmd, *args)
def key_press_event(self, widget, event, data=None):
self.mplayer_exe("key_down_event", event.keyval)
def redraw(self, draw, event, data=None):
self.draw.queue_draw()
def play(self, path):
with MPlayer(path, self.draw, True) as self.mplayer:
gobject.child_watch_add(self.mplayer.pid, gtk.main_quit)
gtk.main()
if __name__ == "__main__":
wrapper = MPlayerWrapper()
wrapper.play(sys.argv[1])
Solved it -- the solution was to add
-vo gl
to the call to mplayer. That is, I replaced
cmd = string.split("mplayer -slave -wid %d -input file=%s" % \
(self.draw.window.xid, self.fifo))
with
cmd = string.split("mplayer -slave -vo gl -wid %d -input file=%s" % \
(self.draw.window.xid, self.fifo))
in the above code, and now it resizes correctly.
That command line option has actually removed the whole reason I wanted to create this wrapper script in the first place (i.e. to embed mplayer into a window with black borders taking up the space not required by the movie itself, in order to use it in dwm), but I imagine this fix would still be useful for other people wanting to embed mplayer for other reasons.