I am using QProcess to run a typical cli command such as 'ping' or 'netstat'. I would like it to run continuously until I tell it to stop. I've provided a cut down version of my code below that uses the "ping" command. If I run the command "ping -t 192.168.0.1 > test.txt" from a cmd prompt it works fine, but when I try to run this in my program below, it produces the error "Bad parameter > test.txt."
It appears that saving the output of cli commands does not count as a recognized argument/parameter for QProcess (the rest of the code works fine as far as I can tell).
from PyQt5 import QtCore, QtGui, QtWidgets
import os
import psutil
## Define origin path.
scriptpath = os.path.realpath(__file__)
scriptpath = scriptpath.replace(os.path.basename(__file__), "")
os.chdir(scriptpath)
origin = os.getcwd()
## Establish UI and interactions.
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
# event actions
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QVBoxLayout(central_widget)
process_btn = QtWidgets.QPushButton("process")
process_btn.clicked.connect(self.process)
end_btn = QtWidgets.QPushButton("end")
end_btn.clicked.connect(self.end)
lay.addWidget(process_btn)
lay.addWidget(end_btn)
self.process = QtCore.QProcess(self)
self._pid = -1
#QtCore.pyqtSlot()
def process(self):
program = 'ping'
arguments = ['-t', '192.168.0.1', '> test.txt'] # Ping 10 times to the router address and save output.
self.process.setProgram(program)
self.process.setArguments(arguments)
ok, pid = self.process.startDetached()
if ok:
self._pid = pid
#QtCore.pyqtSlot()
def end(self):
if self._pid > 0:
p = psutil.Process(self._pid)
p.terminate()
self._pid = -1
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Is there a way to save the output of cli commands run through QProcess to a text file?
Note: I have tried to implement the same feature with subprocess.call('ping -t 192.168.0.1 > test.txt', shell=True) but then I ran into issues with being able to stop the ping. The only way I could stop it was to use the exit command in the cli from where my PyQt5 program was run (closing the app just left the text file continuing to update), which isn't ideal for a program with a GUI. If anyone has a solution to this then perhaps I could go back to it.
Use QProcess setStandardOutputFile method to save process's result into a file.
Related
I am trying to tail a file and output it continuously to a QTextEdit box. However, I have my subprocess and output located within a multiprocess. Here is my code:
shouldRun = True
wMain = QtGui.QWidget()
textboxSideA = QtGui.QTextEdit(wMain)
def tailLog():
subA = subprocess.Popen(["tail", "-F", "<FILENAME>", stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid)
pollA = select.poll()
pollA.register(subA.stdout)
while shouldRun:
if pollA.poll(1):
textboxSideA.append(subA.stdout.readline())
subA.kill()
os.killpg(subA.pid, signal.SIGKILL)
return
processSideA = multiprocessing.Process(target = tailLog)
processSideA.start()
wMain.show()
when the textboxSideA.append is called, the textbox doesn't show anything. I have tried just appending a direct string to it just to make sure it wasn't my readline that was bad. However, that wasn't the issue. I then tried to print out my readline directly to the terminal using print(subA.stdout.readline()) which has worked fine. So I concluded that the QTextEdit textbox GUI isn't being updated. I have tried everything and not even Google has given me an answer.
Also, I can type in the textbox and that shows up properly, and I can save what I have typed. My GUI just doesn't seem to like the multiprocess as I can call .append() outside of the multiprocess and it works just fine.
Qt does not support multiprocessing so it is dangerous to update the GUI from another process, the GUI can only and should be updated from the thread of the process where it was created.
On the other hand in this case it is not necessary to use multiprocessing since you can use QProcess:
import sys
from PyQt4 import QtCore, QtGui
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.process = QtCore.QProcess(self)
self.process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self.process.readyReadStandardOutput.connect(self.on_readyReadStandardOutput)
self.textedit = QtGui.QTextEdit()
self.setCentralWidget(self.textedit)
def tail(self, filename):
self.process.kill()
self.process.start("tail", ["-F", filename])
#QtCore.pyqtSlot()
def on_readyReadStandardOutput(self):
msg = self.process.readAllStandardOutput().data().encode()
self.textedit.append(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = MainWindow()
w.tail("<FILENAME>")
w.show()
sys.exit(app.exec_())
I have a PyQt5 GUI application and would like to execute an external program and display stdout, stderr and stdin of the external program on a QTextEdit widget. I have managed to do so for stdout and stderr. I need help for the stdin of the external process.
Imagine the following snippets
self.te = QTextEdit(self)
self.te.move(self.x0, 150)
self.te.resize(self.mainWinWidth - 100, self.mainWinHeight - 200)
And the snippet to get QProcess going ...
self.process = QtCore.QProcess(self)
self.process.setProcessChannelMode( QProcess.MergedChannels )
self.process.readyRead.connect(self.readReady)
# ... and elsewhere I start the sub process like
os.environ["PYTHONUNBUFFERED"] = "1"
self.process.start('./goo', [])
and readReady() implemented as:
def readReady(self):
cursor = self.te.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.process.readAll(), 'utf-8'))
self.te.ensureCursorVisible()
And goo(1) is a basic sub process implemented as
#!/usr/bin/python3
import time
import sys
for i in range(0,5):
print(f"============ {i} ===")
time.sleep(1)
sys.stderr.write("Testing stderr...\n")
print("Enter name:")
name = sys.stdin.readline()
print(f"Got {name}")
With that said, I do see stdout and stderr all working ok. I also see "Enter name:" but when I enter "joe" or "moe" on the QTextEdit, nothing happens, ie the back end sub process is still waiting.
So it seems like I need an event handler for the write. That is when the sub-process (via QProcess) waits for input on its stdin, I need to detect that and somehow read that from QTextEdit (from the user) and then feed that to the sub process (i.e write to its stdin).
Think ssh or telnet or xterm. Isn't there any on the shelf widget for this ?
You have to use the write() method of QProcess:
├── goo
└── main.py
import os
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._textedit = QtWidgets.QTextEdit(readOnly=True)
self._lineedit = QtWidgets.QLineEdit()
self._pushbutton = QtWidgets.QPushButton("Send")
self._pushbutton.clicked.connect(self.on_clicked)
lay = QtWidgets.QGridLayout(self)
lay.addWidget(self._textedit, 0, 0, 1, 2)
lay.addWidget(self._lineedit, 1, 0)
lay.addWidget(self._pushbutton, 1, 1)
self._process = QtCore.QProcess(self)
self._process.setProcessChannelMode(QtCore.QProcess.MergedChannels)
self._process.readyRead.connect(self.on_readReady)
current_dir = os.path.dirname(os.path.realpath(__file__))
self._process.start(os.path.join(current_dir, "goo"))
#QtCore.pyqtSlot()
def on_readReady(self):
cursor = self._textedit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(str(self._process.readAll(), "utf-8"))
self._textedit.ensureCursorVisible()
#QtCore.pyqtSlot()
def on_clicked(self):
text = self._lineedit.text() + "\n"
self._process.write(text.encode())
if __name__ == "__main__":
os.environ["PYTHONUNBUFFERED"] = "1"
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I'm developing an GUI for multi-robot system using ROS, but i'm freezing in the last thing i want in my interface: embedding the RVIZ, GMAPPING or another screen in my application. I already put an terminal in the interface, but i can't get around of how to add an external application window to my app. I know that PyQt5 have the createWindowContainer, with uses the window ID to dock an external application, but i didn't find any example to help me with that.
If possible, i would like to drag and drop an external window inside of a tabbed frame in my application. But, if this is not possible or is too hard, i'm good with only opening the window inside a tabbed frame after the click of a button.
I already tried to open the window similar to the terminal approach (see the code bellow), but the RVIZ window opens outside of my app.
Already tried to translate the attaching/detaching code code to linux using the wmctrl command, but didn't work wither. See my code here.
Also already tried the rviz Python Tutorial but i'm receveing the error:
Traceback (most recent call last):
File "rvizTutorial.py", line 23, in
import rviz
File "/opt/ros/indigo/lib/python2.7/dist-packages/rviz/init.py", line 19, in
import librviz_shiboken
ImportError: No module named librviz_shiboken
# Frame where i want to open the external Window embedded
self.Simulation = QtWidgets.QTabWidget(self.Base)
self.Simulation.setGeometry(QtCore.QRect(121, 95, 940, 367))
self.Simulation.setTabPosition(QtWidgets.QTabWidget.North)
self.Simulation.setObjectName("Simulation")
self.SimulationFrame = QtWidgets.QWidget()
self.SimulationFrame.setObjectName("SimulationFrame")
self.Simulation.addTab(rviz(), "rViz")
# Simulation Approach like Terminal
class rviz(QtWidgets.QWidget):
def __init__(self, parent=None):
super(rviz, self).__init__(parent)
self.process = QtCore.QProcess(self)
self.rvizProcess = QtWidgets.QWidget(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.rvizProcess)
# Works also with urxvt:
self.process.start('rViz', [str(int(self.winId()))])
self.setGeometry(121, 95, 940, 367)
I've not tested this specifically, as I've an old version of Qt5 I can't upgrade right now, while from Qt5 5.10 startDetached also returns the pid along with the bool result from the started process.
In my tests I manually set the procId (through a static QInputBox.getInt()) before starting the while cycle that waits for the window to be created.
Obviously there are other ways to do this (and to get the xid of the window).
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.embed('xterm')
def embed(self, command, *args):
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
started, procId = proc.startDetached()
if not started:
QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!')
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
if w.get_pid() == procId:
window = QtGui.QWindow.fromWinId(w.get_xid())
container = QtWidgets.QWidget.createWindowContainer(window, self)
self.addTab(container, command)
return
attempts += 1
QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')
app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())
I couldn't get the code in the accepted answer to work on Ubuntu 18.04.3 LTS; even when I got rid of the exceptions preventing the code to run, I'd still get a separate PyQt5 window, and separate xterm window.
Finally after some tries, I got the xterm window to open inside the tab; here is my code working in Ubuntu 18.04.3 LTS (with all the misses commented):
#!/usr/bin/env python3
# (same code seems to run both with python3 and python2 with PyQt5 in Ubuntu 18.04.3 LTS)
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
gi.require_version('Wnck', '3.0')
from gi.repository import Wnck, Gdk
import time
class Container(QtWidgets.QTabWidget):
def __init__(self):
QtWidgets.QTabWidget.__init__(self)
self.embed('xterm')
def embed(self, command, *args):
proc = QtCore.QProcess()
proc.setProgram(command)
proc.setArguments(args)
#started, procId = proc.startDetached()
#pid = None
#started = proc.startDetached(pid)
# https://stackoverflow.com/q/31519215 : "overload" startDetached : give three arguments, get a tuple(boolean,PID)
# NB: we will get a failure `xterm: No absolute path found for shell: .` even if we give it an empty string as second argument; must be a proper abs path to a shell
started, procId = proc.startDetached(command, ["/bin/bash"], ".")
if not started:
QtWidgets.QMessageBox.critical(self, 'Command "{}" not started!'.format(command), "Eh")
return
attempts = 0
while attempts < 10:
screen = Wnck.Screen.get_default()
screen.force_update()
# do a bit of sleep, else window is not really found
time.sleep(0.1)
# this is required to ensure that newly mapped window get listed.
while Gdk.events_pending():
Gdk.event_get()
for w in screen.get_windows():
print(attempts, w.get_pid(), procId, w.get_pid() == procId)
if w.get_pid() == procId:
self.window = QtGui.QWindow.fromWinId(w.get_xid())
#container = QtWidgets.QWidget.createWindowContainer(window, self)
proc.setParent(self)
#self.scrollarea = QtWidgets.QScrollArea()
#self.container = QtWidgets.QWidget.createWindowContainer(self.window)
# via https://vimsky.com/zh-tw/examples/detail/python-method-PyQt5.QtCore.QProcess.html
#pid = proc.pid()
#win32w = QtGui.QWindow.fromWinId(pid) # nope, broken window
win32w = QtGui.QWindow.fromWinId(w.get_xid()) # this finally works
win32w.setFlags(QtCore.Qt.FramelessWindowHint)
widg = QtWidgets.QWidget.createWindowContainer(win32w)
#self.container.layout = QtWidgets.QVBoxLayout(self)
#self.addTab(self.container, command)
self.addTab(widg, command)
#self.scrollarea.setWidget(self.container)
#self.container.setParent(self.scrollarea)
#self.scrollarea.setWidgetResizable(True)
#self.scrollarea.setFixedHeight(400)
#self.addTab(self.scrollarea, command)
self.resize(500, 400) # set initial size of window
return
attempts += 1
QtWidgets.QMessageBox.critical(self, 'Window not found', 'Process started but window not found')
app = QtWidgets.QApplication(sys.argv)
w = Container()
w.show()
sys.exit(app.exec_())
Hi I am a newbie in PyQt.
Can someone suggest why the below program is not populating the stdout of print.py in real time to the textbox widget.This problem exists when it is running from command line.If we are running the samecode in pycharm we will get realtime outputs. Command line execution will show the data as a chunk(approx 4K) to the text box.
from PyQt4 import QtGui,QtCore
import sys
class gui(QtGui.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.process.readAll()))
self.output.ensureCursorVisible()
def initUI(self):
# Layout are better for placing widgets
layout = QtGui.QHBoxLayout()
self.output = QtGui.QTextEdit()
layout.addWidget(self.output)
centralWidget = QtGui.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Run program
self.process.start('python', ['./print.py'])
#Function Main Start
def main():
app = QtGui.QApplication(sys.argv)
ui=gui()
ui.show()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()
Here is the code for print.py
import time
n=10
while(n):
print "Hello world"
time.sleep(1)
n = n-1
You should explicitly flush the output buffer of the print.py script (the child script) running in the subprocess. The default behaviour of print is to send the output to standard out (stdout). As such, you should explicitly call sys.sydout.flush() in the child script, after each print statement, to ensure the content of the print statements from the child script are made available to the parent script in a timely manner.
I have this application that controls a deamon of mine. It basically checks if the deamon is already running, if so it gives the option to close, if not it gives the option to close, also it offers you a log of it, and everything is on a menu that opens from a QSystemTrayIcon. When I run it it works perfectly fine, but when I set it to run automatically after i log in it runs, but the trayicon doesn't show, you can even see the process with "ps aux". I'm running on Ubuntu 12.04.
#!/usr/bin/env python
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from subprocess import call
from subprocess import Popen
import os
import time
class SystemTrayIcon(QtGui.QSystemTrayIcon):
def __init__(self, parent=None):
QtGui.QSystemTrayIcon.__init__(self, parent)
self.setIcon(QtGui.QIcon("./mail_open_process.png"))
global closeAction
global openAction
global startOnLaunch
self.menu = QtGui.QMenu(parent)
openAction = self.menu.addAction("Open")
closeAction = self.menu.addAction("Close")
logAction = self.menu.addAction("Log")
startOnLaunch = self.menu.addAction("Start on Launch")
startOnLaunch.setCheckable(True)
exitAction = self.menu.addAction("Exit")
self.setContextMenu(self.menu)
self.connect(openAction,QtCore.SIGNAL('triggered()'),self.openDaemon)
self.connect(closeAction,QtCore.SIGNAL('triggered()'),self.closeDaemon)
self.connect(logAction,QtCore.SIGNAL('triggered()'),self.openLog)
self.connect(exitAction,QtCore.SIGNAL('triggered()'),self.Exit)
#self.connect(startOnLaunch,QtCore.SIGNAL('triggered()'),self.startLaunch)
if os.path.exists("/dev/repa"):
closeAction.setVisible(True)
openAction.setVisible(False)
else:
closeAction.setVisible(False)
openAction.setVisible(True)
def Exit(self):
os.remove("/tmp/daemonMenu.pid")
sys.exit()
def openDaemon(self):
call(["gksu", os.path.expandvars("$HOME")+"/repad/apps/repad -f"])
time.sleep(1)
if os.path.exists("/dev/repa"):
closeAction.setVisible(True)
openAction.setVisible(False)
def closeDaemon(self):
call(["gksu", os.path.expandvars("$HOME")+"/repad/apps/repad -c"])
time.sleep(1)
if not os.path.exists("/dev/repa"):
closeAction.setVisible(False)
openAction.setVisible(True)
def openLog(self):
Popen(["gedit", "/dev/repad.log"])
#def startLaunch(self):
# Dir = "/etc/init.d/S99scriptDaemon"
# if startOnLaunch.isChecked():
# call(["gksu","cp ./S99scriptDaemon /etc/rc5.d"])
#if not startOnLaunch.isChecked():
# call (["gksu","rm /etc/rc5.d/S99scriptDaemon"])
def main():
pid = str(os.getpid())
pidDir = "/tmp/daemonMenu.pid"
if os.path.isfile(pidDir):
pidFile = open(pidDir,"r")
pidLine = pidFile.readline()
call(["kill", "%s" %(pidLine)])
os.remove(pidDir)
file(pidDir, 'w+').write(pid)
if name == 'main':
main()
app = QtGui.QApplication(sys.argv)
trayIcon = SystemTrayIcon()
trayIcon.show()
sys.exit(app.exec_())
I've got the same issue. I made script start.sh and at startup system starts this script. In script I added 10 second delay before starting application. This script seems like this:
sleep 10
./main.py