代码之家  ›  专栏  ›  技术社区  ›  TheHidden

PyQt5替代Qtermwidget

  •  0
  • TheHidden  · 技术社区  · 4 年前

    我正在尝试寻找Qtermwidget的另一种方法来显示终端输出并接受终端输入(非常类似于标准的linux终端)。

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

    我试图使我的应用程序的设置尽可能简单和快速,大多数依赖是简单的pip包或标准apt安装。

    所以我的问题是:

    有没有一个标准的库或使用像pyqt中输入/输出这样的终端的方法?我曾经考虑过用javascript(足够简单)构建它并使用QWebEngineView,但是这是最好的选择吗?

    0 回复  |  直到 4 年前
        1
  •  2
  •   eyllanesc Yonghwan Shin    4 年前

    一个可能的选择是用纯python编写QTermWidget逻辑以使其可移植,但这可能需要时间,因此在这个答案中,我将使用 xterm.js

    索引.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/xterm@4.5.0/css/xterm.css" />
          <script src="https://cdn.jsdelivr.net/npm/xterm@4.5.0/lib/xterm.js"></script> 
          <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.3.0/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>
    

    索引.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();
            });
        });
    }
    

    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()