Her bir alt süreç için geçerli olacak jenerik bir worker sınıfı tanımlayıp, gelen komutlara göre hangi worker’ın oluşturulacağını veya komutun tipine göre yeni worker oluşturmanın gerekip gerekmediğini denetleyebiliriz.
Yani python3 yazıp enter tuşuna bastığımızda, program python konsolundan çıkana kadar yeni bir worker ve process oluşturmamalı. Öyle değil mi?
Aynı durum nodejs için de geçerli. Eğer yeni bir alt süreç açtıysak, bu alt süreç sonlanana kadar yeni bir süreç açmamak, zaten açılmış olan süreçten devam etmek gerekiyor.
Alt süreçleri yönetmek için SubProcess isimli yeni bir worker sınıf tanımlıyorum. Bu sınıfı, hem python; hem nodejs hem de başka programlar için kullanabiliriz.
import sys
import subprocess
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit
from PyQt5.QtGui import QTextCursor
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class TerminalWorker(QThread):
    finished = pyqtSignal(str)
    def __init__(self, command, parent=None):
        super().__init__(parent)
        self.command = command
    def run(self):
        try:
            process = subprocess.Popen(
                self.command,
                shell=True,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True
            )
            stdout, stderr = process.communicate()
            output = stdout + stderr
        except Exception as e:
            output = str(e)
        self.finished.emit(output)
class SubWorker(QThread):
    finished = pyqtSignal(str)
    def __init__(self, process, parent=None):
        super().__init__(parent)
        self.process = process
    def run(self):
        while True:
            output = self.process.stdout.readline()
            if output:
                self.finished.emit(output.replace(">>> ", ""))
            else:
                break
class CommandLineWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.console = QTextEdit(self)
        self.terminal_process = None
        self.terminal_worker = None
        self.python_process = None
        self.python_worker = None
        self.node_process = None
        self.node_worker = None
        self.config()
    def config(self):
        self.console.setReadOnly(False)
        self.console.setStyleSheet("background-color: black; color: white;")
        self.setCentralWidget(self.console)
        self.console.setTextInteractionFlags(Qt.TextEditorInteraction)
        self.console.setPlaceholderText('Enter commands and press Enter...')
        self.console.installEventFilter(self)
        self.setWindowTitle('Terminal Interface')
        self.setGeometry(100, 100, 800, 600)
    def eventFilter(self, obj, event):
        if obj is self.console and event.type() == event.KeyPress:
            key = event.key()
            if key in (Qt.Key_Enter, Qt.Key_Return):
                self.process_command()
                return True
        return super().eventFilter(obj, event)
    def process_command(self):
        cursor = self.console.textCursor()
        cursor.movePosition(QTextCursor.StartOfBlock)
        cursor.select(QTextCursor.BlockUnderCursor)
        command = cursor.selectedText().strip()
        cursor.removeSelectedText()
        self.console.append(f'{command}\n')
        if command == "python3" and not self.python_process:
            self.start_python_process()
        elif command == "node" and not self.node_process:
            self.start_node_process()
        elif self.node_process:
            self.execute_node_command(command)
        elif self.python_process:
            self.execute_python_command(command)
        else:
            self.execute_terminal_command(command)
    def start_python_process(self):
        self.python_process = subprocess.Popen(
            ["python3", "-i"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1
        )
        self.python_worker = SubWorker(self.python_process)
        self.python_worker.finished.connect(self.subprocess_finished)
        self.python_worker.start()
    def start_node_process(self):
        self.node_process = subprocess.Popen(
            ["node", "-i"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1
        )
        self.node_worker = SubWorker(self.node_process)
        self.node_worker.finished.connect(self.subprocess_finished)
        self.node_worker.start()
    def execute_node_command(self, command):
        if self.node_process:
            self.node_process.stdin.write(f'{command}\n')
            self.node_process.stdin.flush()
            if command == "exit":
                self.node_process = False
    def execute_python_command(self, command):
        if self.python_process:
            self.python_process.stdin.write(f'{command}\n')
            self.python_process.stdin.flush()
            if command == "exit()":
                self.python_process = False
    def execute_terminal_command(self, command):
        self.terminal_worker = TerminalWorker(command)
        self.terminal_worker.finished.connect(self.terminal_process_finished)
        self.terminal_worker.start()
    def subprocess_finished(self, output):
        self.console.append(output)
    def terminal_process_finished(self, data):
        self.console.append(data)
if __name__ == '__main__':
    app = QApplication(sys.argv)
    terminal = CommandLineWindow()
    terminal.show()
    sys.exit(app.exec_())
Burada artık pseudo bir sınıf kullanmadık. Açtığımız prosesleri yönetmeye çalıştık.
Örneğin, yukarıda, üç adet worker tanımlandığını görüyorsunuz.
İlk worker, terminal kodlarını çalıştırmak için,
İkinci worker, python kodlarını çalıştırmak için,
Üçüncü worker da node kodlarını çalıştırmak için oluşturulmuş workerlar.
İkinci worker, command, python3 olduğunda, üçüncü worker ise command node olduğunda çalışıyor.
    def process_command(self):
        cursor = self.console.textCursor()
        cursor.movePosition(QTextCursor.StartOfBlock)
        cursor.select(QTextCursor.BlockUnderCursor)
        command = cursor.selectedText().strip()
        cursor.removeSelectedText()
        self.console.append(f'{command}\n')
        if command == "python3" and not self.python_process:
            self.start_python_process()
        elif command == "node" and not self.node_process:
            self.start_node_process()
        elif self.node_process:
            self.execute_node_command(command)
        elif self.python_process:
            self.execute_python_command(command)
        else:
            self.execute_terminal_command(command)
Yukarıdaki koşul ifadelerini inceleyelim:
İlk koşul ifadesi, eğer command’ın değeri "python3" ise ve bir python süreci açılmamışsa, anlamına geliyor. Bu durumda aşağıdaki fonksiyon çağrılır:
    def start_python_process(self):
        self.python_process = subprocess.Popen(
            ["python3", "-i"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1
        )
        self.python_worker = SubWorker(self.python_process)
        self.python_worker.finished.connect(self.subprocess_finished)
        self.python_worker.start()
İkinci koşul ifadesi ise, eğer command’ın değeri "node" ise ve bir node süreci açılmamışsa, anlamına geliyor. Bu durumda da aşağıdaki fonksiyon çağrılır:
    def start_node_process(self):
        self.node_process = subprocess.Popen(
            ["node", "-i"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            bufsize=1
        )
        self.node_worker = SubWorker(self.node_process)
        self.node_worker.finished.connect(self.subprocess_finished)
        self.node_worker.start()
elif self.node_process: ifadesi, node süreci açıldığında çalışır ve execute_node_command fonksiyonu çağrılır:
    def execute_node_command(self, command):
        if self.node_process:
            self.node_process.stdin.write(f'{command}\n')
            self.node_process.stdin.flush()
            if command == "exit":
                self.node_process = False
elif self.python_process: ifadesi ise, python3 süreci açıldığında çalışır ve execute_python_command fonksiyonu çağrılır:
    def execute_python_command(self, command):
        if self.python_process:
            self.python_process.stdin.write(f'{command}\n')
            self.python_process.stdin.flush()
            if command == "exit()":
                self.python_process = False
Her iki execute fonksiyonunda da yeni prosesler açılmadığını, mevcut proseslere komutların işlendiğini görüyorsunuz.
Bu arada artık python konsolunu aktif ettikten sonra, input fonksiyonunu kullanabilirsiniz.
Ancak hala terminal davranışının alıştığınız python konsoluna benzemesi için azı manipülasyonlar yapmanız gerekir.
Öte yandan, geri kalan diğer terminal işlemleri, şimdilik aşağıdaki fonksiyon yardımıyla çalıştırılır:
    def execute_terminal_command(self, command):
        self.terminal_worker = TerminalWorker(command)
        self.terminal_worker.finished.connect(self.terminal_process_finished)
        self.terminal_worker.start()
execute_terminal_command fonksiyonundaki her bir terminal çağrısı, birbirinden bağımsız süreçler olarak yaratılır.