python - PyQt5 替代 Qtermwidget

标签 python python-3.x pyqt5

我正在尝试寻找 Qtermwidget 的替代方法来显示终端输出并接受终端输入(很像标准的 Linux 终端)。

我遇到的唯一问题是,在目标操作系统(ubuntu)上,由于某些问题,必须手动编译并重新安装它。

我试图使我的应用程序的设置尽可能简单快捷,大多数依赖项都是简单的 pip 包或标准 apt-installs。

所以我的问题是:

是否有标准库或使用终端的方法,例如输入/输出放入pyqt中?我考虑过用 javascript 构建它(足够简单)并使用 QWebEngineView 但这是最​​好的选择吗?

最佳答案

一个可能的选择是用纯Python编写QTermWidget逻辑以使其可移植,但这可能需要时间,所以在这个答案中我将使用xterm.js来实现逻辑。在 QWebChannel 的帮助下:

index.html

<!doctype html>
  <html>
    <head>
      <style>
      * { padding: 0; margin: 0; }
      html, body, #terminal-container {
          min-height: 100% !important;
          width: 100%;
          height: 100%;
      }
      #terminal-container {
          background: black;
      }
      </style>
      <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="3c4448594e517c081209120c" rel="noreferrer noopener nofollow">[email protected]</a>/css/xterm.css" />
      <script src="https://cdn.jsdelivr.net/npm/<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="a9d1ddccdbc4e99d879c8799" rel="noreferrer noopener nofollow">[email protected]</a>/lib/xterm.js"></script> 
      <script src="https://cdn.jsdelivr.net/npm/<a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e79f9382958aca8683838889ca818e93a7d7c9d4c9d7" rel="noreferrer noopener nofollow">[email protected]</a>/lib/xterm-addon-fit.js"></script> 
      <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
      <script type="text/javascript" src="index.js"></script>
    </head>
    <body>
      <div id="terminal-container"></div>
    </body>
  </html>

index.js

window.onload = function () {
    const terminal = new Terminal();
    f = new FitAddon.FitAddon();
    terminal.loadAddon(f);
    const container = document.getElementById('terminal-container');
    terminal.open(container);
    terminal.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
    f.fit();
    new QWebChannel(qt.webChannelTransport, function (channel) {
        var socket = channel.objects.socket;
        var resize_listener = channel.objects.resize_listener;
        terminal.onKey(function(e){
            socket.send_data(e.key)
        });
        socket.dataChanged.connect(function(text){
            terminal.write(text)
        });
        resize_listener.resized.connect(function(){
            f.fit();
        });
    });
}

ma​​in.py

from functools import cached_property
import os

from PyQt5 import QtCore, QtWidgets, QtNetwork, QtWebEngineWidgets, QtWebChannel

CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))


class TerminalSocket(QtNetwork.QTcpSocket):
    dataChanged = QtCore.pyqtSignal(str)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.readyRead.connect(self._handle_ready_read)
        self.error.connect(self._handle_error)

    @QtCore.pyqtSlot(str)
    def send_data(self, message):
        self.write(message.encode())

    def _handle_ready_read(self):
        data = self.readAll().data()
        self.dataChanged.emit(data.decode())

    def _handle_error(self):
        print(self.errorString())


class ResizeListener(QtCore.QObject):
    resized = QtCore.pyqtSignal()

    def __init__(self, widget):
        super().__init__(widget)
        self._widget = widget
        if isinstance(self.widget, QtWidgets.QWidget):
            self.widget.installEventFilter(self)

    @property
    def widget(self):
        return self._widget

    def eventFilter(self, obj, event):
        if obj is self.widget and event.type() == QtCore.QEvent.Resize:
            QtCore.QTimer.singleShot(100, self.resized.emit)
        return super().eventFilter(obj, event)


class TerminalWidget(QtWebEngineWidgets.QWebEngineView):
    def __init__(self, ipaddr, port, parent=None):
        super().__init__(parent)

        resize_listener = ResizeListener(self)
        self.page().setWebChannel(self.channel)
        self.channel.registerObject("resize_listener", resize_listener)
        self.channel.registerObject("socket", self.socket)
        filename = os.path.join(CURRENT_DIR, "index.html")
        self.load(QtCore.QUrl.fromLocalFile(filename))
        self.socket.connectToHost(ipaddr, port)

    @cached_property
    def socket(self):
        return TerminalSocket()

    @cached_property
    def channel(self):
        return QtWebChannel.QWebChannel()


def main():
    import sys

    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 = TerminalWidget(QtNetwork.QHostAddress(address), int(port))
    w.resize(640, 480)
    w.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

关于python - PyQt5 替代 Qtermwidget,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62479231/

相关文章:

python - PigLatin 翻译器,用于以多个辅音开头的单词[Python]

python - 更改 Holoviewplot 上的 x 或 y 轴 (hv.BoxWhisker)

python - 使我的脚本打印结果时出现问题

python - 在 lambda 中自动元组解包的好的 python3 等价物是什么?

python - Python OpenCV形状检测在白板上不起作用

python - 在QGraphicsItem上添加动画时遇到的一个问题

python - PyQt5:如何对齐状态栏的元素?

python - Scrapy 将子站点项与站点项合并

python nltk处理文本,快速去除停用词

pyqt5 - 通过索引访问时,QTableWidget 拖/放行不会更改其位置