pythonpyqtpyqt5python-3.8

PyQT terminal emulator


I am trying to develop a "console" in pyqt. Similar to xterm.js where all the console is, is front end it does not spawn any sub-processes its just an I/O for me to plug in whatever I want to at a later date.

are there any existing python packages or simple widgets I can use that would allow me to put a terminal like interface within my pyqt application?

its a client server application so the terminal is used to send commands to the backend server and retrieve output as if it were a bash shell (for example)


Solution

  • You can use QTermWidget (if you can't install it and you're using ubuntu then you can check this answer).

    For example a translation of the official RemoteTerm example that allows to access the shell remotely through sockets would be the following:

    terminal.py

    import os
    import sys
    
    from PyQt5 import QtCore, QtWidgets, QtNetwork
    
    import QTermWidget
    
    
    class RemoteTerm(QTermWidget.QTermWidget):
        def __init__(self, ipaddr, port, parent=None):
            super().__init__(0, parent)
    
            self.socket = QtNetwork.QTcpSocket(self)
    
            self.socket.error.connect(self.atError)
            self.socket.readyRead.connect(self.on_readyRead)
            self.sendData.connect(self.socket.write)
    
            self.startTerminalTeletype()
            self.socket.connectToHost(ipaddr, port)
    
        @QtCore.pyqtSlot()
        def on_readyRead(self):
            data = self.socket.readAll().data()
            os.write(self.getPtySlaveFd(), data)
    
        @QtCore.pyqtSlot()
        def atError(self):
            print(self.socket.errorString())
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
    
        QtCore.QCoreApplication.setApplicationName("QTermWidget Test")
        QtCore.QCoreApplication.setApplicationVersion("1.0")
    
        parser = QtCore.QCommandLineParser()
        parser.addHelpOption()
        parser.addVersionOption()
        parser.setApplicationDescription(
            "Example(client-side) for remote terminal of QTermWidget"
        )
        parser.addPositionalArgument("ipaddr", "adrress of host")
        parser.addPositionalArgument("port", "port of host")
    
        parser.process(QtCore.QCoreApplication.arguments())
    
        requiredArguments = parser.positionalArguments()
        if len(requiredArguments) != 2:
            parser.showHelp(1)
            sys.exit(-1)
    
        address, port = requiredArguments
        w = RemoteTerm(QtNetwork.QHostAddress(address), int(port))
        w.resize(640, 480)
        w.show()
        sys.exit(app.exec_())
    

    shell-srv.py

    #!/usr/bin/env python
    
    import sys
    import os
    import socket
    import pty
    
    
    def usage(program):
        print("Example(server-side) for remote terminal of QTermWidget.")
        print("Usage: %s ipaddr port" % program)
    
    
    def main():
        if len(sys.argv) != 3:
            usage(sys.argv[0])
            sys.exit(1)
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.bind((sys.argv[1], int(sys.argv[2])))
            s.listen(0)
            print("[+]Start Server.")
        except Exception as e:
            print("[-]Error Happened: %s" % e.message)
            sys.exit(2)
    
        while True:
            c = s.accept()
            os.dup2(c[0].fileno(), 0)
            os.dup2(c[0].fileno(), 1)
            os.dup2(c[0].fileno(), 2)
    
            # It's important to use pty to spawn the shell.
            pty.spawn("/bin/sh")
            c[0].close()
    
    
    if __name__ == "__main__":
        main()