1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-03-04 16:02:51 +01:00

fix: terminal I/O based on socket notification instead of thread, fix "unexpected end of data"

This commit is contained in:
2024-06-11 13:57:08 +02:00
committed by wyzula-jan
parent 2b5068903a
commit 74182bb142

View File

@@ -1,13 +1,92 @@
import logging
import os
import platform
import sys
import termqt
from qtpy.QtCore import Qt
from qtpy.QtCore import QSocketNotifier, Qt, pyqtRemoveInputHook
from qtpy.QtGui import QFont
from qtpy.QtWidgets import QApplication, QHBoxLayout, QScrollBar, QWidget
from termqt import Terminal
pyqtRemoveInputHook()
if platform.system() in ["Linux", "Darwin"]:
terminal_cmd = os.environ["SHELL"]
from termqt import TerminalPOSIXExecIO
class TerminalExecIO(TerminalPOSIXExecIO):
def _read_loop(self):
pass
def find_utf8_split(self, data):
"""UTF-8 characters can be 1-4 bytes long, this finds first index which is not mid character
Character lengths include:
1 Bytes: 0xxxxxxx
2 Bytes: 110xxxxx 10xxxxxx
3 Bytes: 1110xxxx 10xxxxxx 10xxxxxx
4 bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Source: https://en.wikipedia.org/wiki/UTF-8#Encoding
Start at end of chunk moving backwards, find first UTF-8 start byte:
1 Bytes: 0xxxxxxx - 0x80 == 0x00
2 Bytes: 110xxxxx - 0xE0 == 0xC0
3 Bytes: 1110xxxx - 0xF0 == 0xE0
4 bytes: 11110xxx - 0xF8 == 0xF0
Parameters:
data (bytes) - buffer to be evaluated
Returns:
(int) - last position of complete UTF-8 character
"""
pos = 0
for i, c in enumerate(reversed(data)):
if c & 0x80 == 0x00 or c & 0xE0 == 0xC0 or c & 0xF0 == 0xE0 or c & 0xF8 == 0xF0:
pos = i
break
return len(data) - pos
def _read(self, fd):
try:
data = os.read(fd, 2**16) # read as much as possible
except OSError:
data = b""
if data:
self._read_buf += data
i = self.find_utf8_split(self._read_buf)
output = self._read_buf[:i]
self._read_buf = self._read_buf[i:]
self.stdout_callback(output)
else:
self.logger.info("Spawned process has been killed")
if self.running:
self.running = False
self.terminated_callback()
os.close(fd)
def spawn(self):
super().spawn()
self._read_notifier = QSocketNotifier(self.fd, QSocketNotifier.Read)
self._read_notifier.activated.connect(self._read)
def write(self, buffer):
# same as original method, but without logging and without assert (unneeded)
if not self.running:
return
try:
os.write(self.fd, buffer)
except OSError:
self.running = False
self.terminated_callback()
else:
terminal_cmd = "cmd.exe"
from termqt import TerminalWinptyIO as TerminalExecIO
class TerminalWidget(QWidget):
def __init__(self, logger):
@@ -53,30 +132,13 @@ class BECConsole(QWidget):
return logger
def setup_terminal_io(self):
if self.platform in ["Linux", "Darwin"]:
bin = "/bin/bash"
from termqt import TerminalPOSIXExecIO
self.terminal_io = TerminalPOSIXExecIO(
self.terminal_widget.terminal.row_len,
self.terminal_widget.terminal.col_len,
bin,
logger=self.logger,
)
elif self.platform == "Windows":
bin = "cmd"
from termqt import TerminalWinptyIO
self.terminal_io = TerminalWinptyIO(
self.terminal_widget.terminal.row_len,
self.terminal_widget.terminal.col_len,
bin,
logger=self.logger,
)
self.auto_wrap_enabled = False
else:
self.logger.error(f"Not supported platform: {self.platform}")
sys.exit(-1)
self.terminal_io = TerminalExecIO(
self.terminal_widget.terminal.row_len,
self.terminal_widget.terminal.col_len,
terminal_cmd,
logger=self.logger,
)
self.auto_wrap_enabled = False
self.terminal_widget.terminal.enable_auto_wrap(self.auto_wrap_enabled)
self.terminal_io.stdout_callback = self.terminal_widget.terminal.stdout