Search code examples
python-3.xterminalpyqt5urxvt

Python PyQt5 Terminal Command execution problem on a button click


I'm trying to create an application, that can execute an embedded Terminal command when ever the button is clicked. The actual problem occurs when i click the button and nothing happens. I have two scripts one has a terminal widget and the other has the main GUI. Any Help, would be highly appreciated.

That's first Script

import sys
from PyQt5 import QtCore, QtWidgets


class EmbTerminal(QtWidgets.QWidget):

    def __init__(self, parent=None):
        super(EmbTerminal, self).__init__(parent)
        self._process = []
        self.start_process('urxvt',['-embed', str(int(self.winId())),"-e","tmux"])

    def start_process(self,prog,options):
        child = QtCore.QProcess(self)
        self._process.append(child)
        child.start(prog,options)

    def run_command(self, command = "ls" ):
        program = "tmux"
        options = []
        options.extend(["send-keys"])
        options.extend([command])
        options.extend(["Enter"])
        self.start_process(program, options)

That's Second Script

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_Dialog(object):

    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(745, 496)
        self.tabWidget = QtWidgets.QTabWidget(Dialog)
        self.tabWidget.setGeometry(QtCore.QRect(100, 190, 561, 261))
        self.tabWidget.setObjectName("tabWidget")
        self.tab = QtWidgets.QWidget()
        self.tab.setObjectName("tab")
        self.tabWidget.addTab(EmbTerminal(), "Terminal")
        self.tab_2 = QtWidgets.QWidget()
        self.tab_2.setObjectName("tab_2")
        self.tabWidget.addTab(self.tab_2, "")
        self.pushButton = QtWidgets.QPushButton(Dialog)
        self.pushButton.setGeometry(QtCore.QRect(280, 70, 211, 71))
        self.pushButton.setObjectName("pushButton")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

        self.pushButton.clicked.connect(lambda: EmbTerminal.run_command(EmbTerminal(), "ls"))

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Dialog", "Tab 1"))
        self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Dialog", "Tab 2"))
        self.pushButton.setText(_translate("Dialog", "ls"))

from terminal5 import EmbTerminal

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Dialog = QtWidgets.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())

Solution

  • You should consider the following:

    • You should not modify the code generated by Qt Designer (unless you understand its logic)
    • Do not implement the logic in the class generated by Qt Designer, it is advisable to create a new class that inherits from the appropriate widget and use the other class to fill it.

    In your case the problem is that the EmbTerminal() object in lambda: EmbTerminal.run_command(EmbTerminal(), "ls") only exists while the lambda is running, but the lambda runs for a very short time causing the command not to be sent causing the error.

    Considering the above, the solution is:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    
    class Ui_Dialog(object):
        def setupUi(self, Dialog):
            Dialog.setObjectName("Dialog")
            Dialog.resize(745, 496)
            self.tabWidget = QtWidgets.QTabWidget(Dialog)
            self.tabWidget.setGeometry(QtCore.QRect(100, 190, 561, 261))
            self.tabWidget.setObjectName("tabWidget")
            self.pushButton = QtWidgets.QPushButton(Dialog)
            self.pushButton.setGeometry(QtCore.QRect(280, 70, 211, 71))
            self.pushButton.setObjectName("pushButton")
    
            self.retranslateUi(Dialog)
            QtCore.QMetaObject.connectSlotsByName(Dialog)
    
        def retranslateUi(self, Dialog):
            _translate = QtCore.QCoreApplication.translate
            Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
            self.pushButton.setText(_translate("Dialog", "ls"))
    
    
    from terminal5 import EmbTerminal
    
    
    class Dialog(QtWidgets.QDialog, Ui_Dialog):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.setupUi(self)
    
            self.terminal = EmbTerminal()
            self.tabWidget.addTab(self.terminal, "Terminal")
            self.pushButton.clicked.connect(self.on_clicked)
    
        @QtCore.pyqtSlot()
        def on_clicked(self):
            self.terminal.run_command("ls")
    
    
    if __name__ == "__main__":
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
        w = Dialog()
        w.show()
        sys.exit(app.exec_())
    

    On the other hand if you are only going to send commands then it is not necessary to store the QProcess, instead use QProcess: startDetached and make run_command a classmethod:

    class EmbTerminal(QtWidgets.QWidget):
        # ...
    
        @staticmethod
        def run_command(command = "ls" ):
            program = "tmux"
            options = []
            options.extend(["send-keys"])
            options.extend([command])
            options.extend(["Enter"])
            QtCore.QProcess.startDetached(program, options)