我正在尝试寻找 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();
});
});
}
main.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/