PyQt5 pass command to embedded terminal 'urxvt' or 'xterm'

591 views Asked by At

I already complete embedded terminal to PyQt5 application follow this answer.

Now I want to use buttons to send command to this embedded terminal similar here.

Ex:

Button_1 send "ifconfig", 
Button_2 send "ping 127.0.0.1". 

My code:

import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QPushButton


class EmbTerminal(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(EmbTerminal, self).__init__(parent)
        self.process = QtCore.QProcess(self)
        self.terminal = QtWidgets.QWidget(self)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.terminal)
        # Works also with urxvt:
        # self.process.start('urxvt',['-embed', str(int(self.winId()))])
        self.process.start('xterm',['-into', str(int(self.winId()))])
        # self.setFixedSize(640, 480)

        button1 = QPushButton('ifconfig')
        layout.addWidget(button1)
        button1.clicked.connect(self.button_1_clicked)

        button2 = QPushButton('ping 127.0.0.1')
        layout.addWidget(button2)
        button2.clicked.connect(self.button_2_clicked)

    def button_1_clicked(self):
        print('send \"ifconfig\" to embedded termial')

    def button_2_clicked(self):
        print('send \"ping 127.0.0.1\" to embedded termial')

class mainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(mainWindow, self).__init__(parent)

        central_widget = QtWidgets.QWidget()
        lay = QtWidgets.QVBoxLayout(central_widget)
        self.setCentralWidget(central_widget)

        tab_widget = QtWidgets.QTabWidget()
        lay.addWidget(tab_widget)

        tab_widget.addTab(EmbTerminal(), "EmbTerminal")
        tab_widget.addTab(QtWidgets.QTextEdit(), "QTextEdit")
        tab_widget.addTab(QtWidgets.QMdiArea(), "QMdiArea")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    main = mainWindow()
    main.show()
    sys.exit(app.exec_())

How can I do it?

I already tried other answer(attach another application to PyQt5 window) and answer(pass command to QProcess C++ not Python)

2

There are 2 answers

1
eyllanesc On BEST ANSWER

One possible solution is to use tmux as an intermediary to send the commands:

import sys
import time
import uuid

from PyQt5 import QtCore, QtGui, QtWidgets

import gi

gi.require_version("Wnck", "3.0")
from gi.repository import Wnck, Gdk


class TerminalContainer(QtWidgets.QTabWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        lay = QtWidgets.QVBoxLayout(self)
        lay.setContentsMargins(0, 0, 0, 0)
        self.name_session = uuid.uuid4().hex

    def start(self):
        started, procId = QtCore.QProcess.startDetached(
            "xterm", ["-e", "tmux", "new", "-s", self.name_session], "."
        )
        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()
            time.sleep(0.1)
            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:
                    win32w = QtGui.QWindow.fromWinId(w.get_xid())
                    widg = QtWidgets.QWidget.createWindowContainer(win32w)
                    self.layout().addWidget(widg)
                    self.resize(500, 400)
                    return
            attempts += 1
        QtWidgets.QMessageBox.critical(
            self, "Window not found", "Process started but window not found"
        )

    def stop(self):
        QtCore.QProcess.execute("tmux", ["kill-session", "-t", self.name_session])

    def send_command(self, command):
        QtCore.QProcess.execute(
            "tmux", ["send-keys", "-t", self.name_session, command, "Enter"]
        )


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.ifconfig_btn = QtWidgets.QPushButton("ifconfig")
        self.ping_btn = QtWidgets.QPushButton("ping")
        self.terminal = TerminalContainer()

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QGridLayout(central_widget)
        lay.addWidget(self.ifconfig_btn, 0, 0)
        lay.addWidget(self.ping_btn, 0, 1)
        lay.addWidget(self.terminal, 1, 0, 1, 2)

        self.terminal.start()

        self.resize(640, 480)

        self.ifconfig_btn.clicked.connect(self.launch_ifconfig)
        self.ping_btn.clicked.connect(self.launch_ping)

    def launch_ifconfig(self):
        self.terminal.send_command("ifconfig")

    def launch_ping(self):
        self.terminal.send_command("ping 8.8.8.8")

    def closeEvent(self, event):
        self.terminal.stop()
        super().closeEvent(event)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())
0
Đức Hải Trần On

After combining the above-accepted answer with this answer, I am already finishing my required.

This is combined code, running on Python2-Qt5

import sys
from PyQt5 import QtCore, QtGui, QtWidgets
import gi
import uuid
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):
        self.name_session = uuid.uuid4().hex
        proc = QtCore.QProcess()
        proc.setProgram(command)
        proc.setArguments(args)

        started, procId = QtCore.QProcess.startDetached(
            "xterm", ["-e", "tmux", "new", "-s", self.name_session], "."
        )
        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())
                    proc.setParent(self)
                    win32w = QtGui.QWindow.fromWinId(w.get_xid())
                    win32w.setFlags(QtCore.Qt.FramelessWindowHint)
                    widg = QtWidgets.QWidget.createWindowContainer(win32w)

                    self.addTab(widg, 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')

    def stop(self):
        QtCore.QProcess.execute("tmux", ["kill-session", "-t", self.name_session])

    def send_command(self, command):
        QtCore.QProcess.execute(
            "tmux", ["send-keys", "-t", self.name_session, command, "Enter"]
        )

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)

        self.ifconfig_btn = QtWidgets.QPushButton("ifconfig")
        self.ping_btn = QtWidgets.QPushButton("ping")
        self.terminal = Container()

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QGridLayout(central_widget)
        lay.addWidget(self.ifconfig_btn, 0, 0)
        lay.addWidget(self.ping_btn, 0, 1)
        lay.addWidget(self.terminal, 1, 0, 1, 2)

        self.resize(640, 480)

        self.ifconfig_btn.clicked.connect(self.launch_ifconfig)
        self.ping_btn.clicked.connect(self.launch_ping)

    def launch_ifconfig(self):
        self.terminal.send_command("ifconfig")

    def launch_ping(self):
        self.terminal.send_command("ping 8.8.8.8")

    def closeEvent(self, event):
        self.terminal.stop()
        super().closeEvent(event)

app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())