class ProcessHandler(QObject):

    commandReceived = pyqtSignal(str)
    commandSent = pyqtSignal(str)
    processFinished = pyqtSignal()
    processError = pyqtSignal(str)

    def __init__(self, executable, parent=None):

        QObject.__init__(self, parent)
        self.executable = executable

    def run(self):

        self.process = QProcess()
        self.process.readyReadStandardOutput.connect(self.handleInput)
        self.process.setReadChannel(QProcess.StandardOutput)
        self.process.closeReadChannel(QProcess.StandardError)
        self.process.finished.connect(self.processFinished)
        self.pendingInput = QByteArray()
        self.in_message = False
        self.inputExpected = 0
        self.pendingOutput = QByteArray()
        self.process.start(self.executable)

        # On Qt 5.6 and later, we can use the errorOccurred signal instead.
        if not self.process.waitForStarted(-1):
            self.processError.emit("The application quit unexpectedly.")

    def quit(self):

        print("Terminating %i" % self.process.processId())
        self.process.closeReadChannel(QProcess.StandardOutput)
        self.process.closeReadChannel(QProcess.StandardError)
        self.process.closeWriteChannel()
        self.process.terminate()
        self.process.waitForFinished()
        self.thread().quit()

    def handleInput(self):

        self.pendingInput += self.process.readAllStandardOutput()

        try:
            while self.pendingInput.size() > 0:

                if not self.in_message:
                    space = self.pendingInput.indexOf(b" ")
                    if space == -1:
                        return

                    # Specify UTF-8 instead of falling back on something implicit.
                    self.inputExpected = int(
                        str(self.pendingInput.left(space), "utf8"))

                    # Examine the rest of the input.
                    self.pendingInput = self.pendingInput.mid(space + 1)
                    self.in_message = True

                # Try to read the rest of the message.
                if len(self.pendingInput) >= self.inputExpected:

                    command = self.pendingInput.left(self.inputExpected)

                    self.pendingInput = self.pendingInput.mid(
                        self.inputExpected)
                    self.in_message = False
                    self.inputExpected = 0
                    self.commandReceived.emit(str(command, "utf8"))

                elif self.process.bytesAvailable() > 0:
                    self.pendingInput += self.process.readAllStandardOutput()
                else:
                    return

        except ValueError:
            self.processError.emit(str(self.pendingInput, "utf8"))

    def handleOutput(self, message):

        # Write the length of the message and the message itself.
        message = "%i %s" % (len(message), message)
        self.pendingOutput += QByteArray(bytes(message, "utf8"))

        while self.pendingOutput.size() > 0:

            written = self.process.write(self.pendingOutput)

            if written == -1:
                self.processError.emit("Failed to write to application.")
                return

            # Handle the rest of the output.
            self.pendingOutput = self.pendingOutput.mid(written)

        self.commandSent.emit(message)