how to use a terminal embedded in a PyQt GUI - python

There is an existing environment and framework usable via Bash terminal around which I want to make a GUI. What I have in mind is the following flow:
In a Bash session, the framework environment is set up. This results in everything from environment variables to authentications being set up in the session.
A Python GUI script is run in order to wrap around the existing session and make it easier to run subsequent steps.
The GUI appears, displaying on one side the Bash session in an embedded terminal and on the other side a set of buttons corresponding to various commands that can be run in the existing framework environment.
Buttons can be pressed in the GUI resulting in certain Bash commands being run. The results of the runs are displayed in the embedded terminal.
What is a good way to approach the creation of such a GUI? I realise that the idea of interacting with the existing environment could be tricky. If it is particularly tricky, I am open to recreating the environment in a session of the GUI. In any case, how can the GUI interact with the embedded terminal. How can commands be run and displayed in the embedded terminal when buttons of the GUI are pressed?
A basic start of the GUI (featuring an embedded terminal) is as follows:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(800, 600)
self.process = QProcess(self)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self.process.start(
'xterm',
['-into', str(self.terminal.winId())]
)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())
How could I run, say, top on this embedded terminal following the press of a button in the GUI?

If it has to be a real terminal and a real shell (and not just accepting a line of input, running some command, then displaying output) -- how about tmux?
You could use something like tee to get the output back into your program.
Note that tmux sessions may persist across your program runs, so you'd need to read up on how that works and how to control it.
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self._processes = []
self.resize(800, 600)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self._start_process(
'xterm',
['-into', str(self.terminal.winId()),
'-e', 'tmux', 'new', '-s', 'my_session']
)
button = QPushButton('List files')
layout.addWidget(button)
button.clicked.connect(self._list_files)
def _start_process(self, prog, args):
child = QProcess()
self._processes.append(child)
child.start(prog, args)
def _list_files(self):
self._start_process(
'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter'])
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())
A bit more here: https://superuser.com/questions/492266/run-or-send-a-command-to-a-tmux-pane-in-a-running-tmux-session

If anyone else comes upon this made some slight mods to it to close the tmux session if it exists since the previous one did not close it on exit. Also set it up for PySide2
About the only thing it needs now is resize support.
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import psutil
import os
import platform
import sys
from pathlib import Path
from subprocess import call
from PySide2 import QtCore
from PySide2.QtCore import *
from PySide2.QtWidgets import QWidget, QVBoxLayout, QPushButton, QApplication
platform = platform.system()
print(str(platform))
term_dir = Path(os.path.abspath(os.path.dirname(__file__))) / 'terminus'
if platform == 'Windows':
term_bin = str(term_dir) + '/' + str(platform.lower()) + '/' + 'terminus.exe'
elif platform == 'Linux':
term_bin = str(term_dir) + '/' + str(platform.lower()) + '/' + 'terminus'
print(term_bin)
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self._processes = []
self.resize(800, 600)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self._stop_process()
self._start_process(
'xterm',
['-into', str(self.terminal.winId()),
'-e', 'tmux', 'new', '-s', 'my_session']
)
button = QPushButton('List files')
layout.addWidget(button)
button.clicked.connect(self._list_files)
def _start_process(self, prog, args):
child = QProcess()
self._processes.append(child)
child.start(prog, args)
def _list_files(self):
self._start_process(
'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter'])
#classmethod
def _stop_process(self):
call(["tmux", "kill-session", "-t", "my_session"])
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())

Related

In PyQt, how can a terminal be embedded in a window?

I have a small script that is designed to embed an xterm in a PyQt GUI. On Linux, it works, creating a GUI like this:
However, running the same script on OS X yields two windows like this:
Does anyone know how to address this and prevent OS X from screwing up the GUI?
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self.resize(800, 600)
self.process = QProcess(self)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self.process.start('xterm', ['-into', str(self.terminal.winId())])
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())
You could take a look at the qtconsole front end for Jupyter and try to use the bash kernel. Depending on your end goal, I know it is possible to embed an IPython kernel, in another application.

PyQt error “QProcess: Destroyed while process is still running”

When I try to run the following PyQt code for running processes and tmux, I encounter the error QProcess: Destroyed while process is still running. How can I fix this?
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class embeddedTerminal(QWidget):
def __init__(self):
QWidget.__init__(self)
self._processes = []
self.resize(800, 600)
self.terminal = QWidget(self)
layout = QVBoxLayout(self)
layout.addWidget(self.terminal)
self._start_process(
'xterm',
['-into', str(self.terminal.winId()),
'-e', 'tmux', 'new', '-s', 'my_session']
)
button = QPushButton('list files')
layout.addWidget(button)
button.clicked.connect(self._list_files)
def _start_process(self, prog, args):
child = QProcess()
self._processes.append(child)
child.start(prog, args)
def _list_files(self):
self._start_process(
'tmux', ['send-keys', '-t', 'my_session:0', 'ls', 'Enter']
)
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
You usually get the error QProcess: Destroyed while process is still running when the application closes and the process hadn't finished.
In your current code, your application ends at soon as it starts, because you didn't call app.exec_(). You should do something like:
if __name__ == "__main__":
app = QApplication(sys.argv)
main = embeddedTerminal()
main.show()
sys.exit(app.exec_())
Now, it works fine, but when you close the application you will still get the error message. You need to overwrite the close event to end the process properly. This works, given you replace child by self.child:
def closeEvent(self,event):
self.child.terminate()
self.child.waitForFinished()
event.accept()

interactive python - keeping console interactive with a GUI mainloop

I am wondering how one would create a GUI application, and interact with it from the console that started it.
As an example, I would like to create a GUI in PyQt and work with it from the console. This could be for testing settings without restarting the app, but in larger projects also for calling functions etc.
Here is a simple example using PyQt:
import sys
from PyQt4 import QtGui
def main():
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
when this is run with python -i example.py the console is blocked as long as the main-loop is executed.
How can I call w.resize(100,100) while the GUI is running?
ops, posted wrong answer before
there is a post in Stack about that
Execute Python code from within PyQt event loop
The following example uses the code module to run a console in the command prompt (be sure to run the script from the command line). Subclassing QThread provides a route by which the console can be run in a separate thread from that of the main GUI and enables some interaction with it. The stub example below should be easy enough to incorporate into a larger packaged PyQt program.
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import threading #used only to id the active thread
import code
import sys
class Worker(QThread): #Subclass QThread and re-define run()
signal = pyqtSignal()
def __init__(self):
super().__init__()
def raise_sys_exit(self): #more gracefully exit the console
print('(Deactivated Console)')
raise SystemExit
def setup_console(self,global_dict):
console_exit = {'exit': self.raise_sys_exit}
self.console = code.InteractiveConsole(locals=dict(global_dict,**console_exit))
def run(self):
try:
print('worker', threading.get_ident())
self.console.interact()
except SystemExit:
self.signal.emit()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args,**kwargs)
self.data = [1,2,3,4] #some data we might want to look at
layout = QVBoxLayout()
self.b = QPushButton("Interact")
self.b.clicked.connect(self.b_clicked)
layout.addWidget(self.b)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.worker = Worker()
self.worker.signal.connect(self.finished)
def finished(self):
self.b.setEnabled(True)
def b_clicked(self):
print('main',threading.get_ident())
self.worker.setup_console(globals()) #pass the global variables to the worker
self.worker.start()
self.b.setEnabled(False) #disable the GUI button until console is exited
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The easiest way is to use IPython:
ipython --gui=qt4
See ipython --help or the online documentation for more options (e.g. gtk, tk, etc).

PyQt SystemTrayIcon won't show when i set the application to start after login

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

PyQt dialog - How to make it quit after pressing a button?

Well, I'm writing a small PyQt4 app, it's just a single Yes/No dialog which has to execute an external command (e.g. 'eject /dev/sr0') and quit.
The app runs, it executes the command after pressing the "Yes" button, but I cannot make the dialog itself exit when executing the command.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import os
import subprocess
from PyQt4 import QtGui
from PyQt4 import QtCore
from subprocess import call
cmd = 'eject /dev/sr0'
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
btn = QtGui.QPushButton('Yes', self)
btn.clicked.connect(lambda: os.system(cmd))
btn.resize(180, 40)
btn.move(20, 35)
qbtn = QtGui.QPushButton('No', self)
qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
qbtn.resize(180, 40)
qbtn.move(20, 80)
self.setWindowTitle('Test')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here is my code. When I click "Yes", it calls the 'eject /dev/sr0' command properly, but after that the dialog is still visible. I have to click "No" to close the app I would like it to close automatically when the command is executed. What should I add/modify?
btn.clicked.connect(self.close)
That would be my suggestion
Replace lambda: os.system(cmd) with a function/method that has multiple statements.
def buttonClicked(self):
os.system(cmd)
QtCore.QCoreApplication.instance().quit()
...
btn = QtGui.QPushButton('Yes', self)
btn.clicked.connect(self.buttonClicked)
...
Step1: in the Main Class needs to be build a "connection":
self.ui.closeButton.clicked.connect(self.closeIt)
Step2: Creating a function like to close:
def closeIt(self):
self.close()
I named to "closeIt" on purpose because if you name it "close" a conflict will occur.
This solution has the advantage if the created GUI is a plugin for another program (like in my case QGIS), only the active GUI will be closed and not the whole program.
Subclass QDialog() and then close it using your object.
class Dialog(QDialog):
"""
Subclassing QDialog class.
"""
def __init__(self):
QDialog.__init__(self)
def close_clicked(self):
self.close()
In your main function write following code
dialogbox = Dialog() # we subclasses QDialog into Dialog
b1= QPushButton("Close",dialogbox)
b1.clicked.connect(dialogbox.close_clicked)
dialogbox.exec_()

Categories