Пример #1
0
class QJackCaptureMainWindow(QDialog):
    sample_formats = {
        "32-bit float": "FLOAT",
        "8-bit integer": "8",
        "16-bit integer": "16",
        "24-bit integer": "24",
        "32-bit integer": "32",
    }

    def __init__(self, parent, jack_client, jack_name=PROGRAM):
        QDialog.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.fFreewheel = False
        self.fLastTime = -1
        self.fMaxTime = 180

        self.fTimer = QTimer(self)
        self.fProcess = QProcess(self)
        self.fJackClient = jack_client
        self.fJackName = jack_name

        self.fBufferSize = self.fJackClient.get_buffer_size()
        self.fSampleRate = self.fJackClient.get_sample_rate()

        # Selected ports used as recording sources
        self.rec_sources = set()

        self.createUi()
        self.loadSettings()
        self.populatePortLists(init=True)

        # listen to changes to JACK ports
        self._refresh_timer = None
        self.fJackClient.ports_changed.connect(self.slot_refreshPortsLists)

    @Slot()
    def slot_refreshPortsLists(self, delay=200):
        if not self._refresh_timer or not self._refresh_timer.isActive():
            log.debug("Scheduling port lists refresh in %i ms...", delay)
            self._refresh_timer = QTimer()
            self._refresh_timer.setSingleShot(True)
            self._refresh_timer.timeout.connect(self.populatePortLists)
            self._refresh_timer.start(delay)

    def populateFileFormats(self):
        # Get list of supported file formats
        self.fProcess.start(gJackCapturePath, ["-pf"])
        self.fProcess.waitForFinished()

        formats = []

        for fmt in str(self.fProcess.readAllStandardOutput(),
                       encoding="utf-8").split():
            fmt = fmt.strip()
            if fmt:
                formats.append(fmt)

        # Put all file formats in combo-box, select 'wav' option
        self.ui.cb_format.clear()
        for i, fmt in enumerate(sorted(formats)):
            self.ui.cb_format.addItem(fmt)

            if fmt == "wav":
                self.ui.cb_format.setCurrentIndex(i)

    def populateSampleFormats(self):
        # Put all sample formats in combo-box, select 'FLOAT' option
        self.ui.cb_depth.clear()
        for i, (label, fmt) in enumerate(self.sample_formats.items()):
            self.ui.cb_depth.addItem(label, fmt)

            if fmt == "FLOAT":
                self.ui.cb_depth.setCurrentIndex(i)

    def populatePortLists(self, init=False):
        log.debug("Populating port lists (init=%s)...", init)
        if init:
            self.outputs_model = QStandardItemModel(0, 1, self)
            self.inputs_model = QStandardItemModel(0, 1, self)
        else:
            self.outputs_model.clear()
            self.inputs_model.clear()

        output_ports = list(self.fJackClient.get_output_ports())
        self.populatePortList(self.outputs_model, self.ui.tree_outputs,
                              output_ports)
        input_ports = list(self.fJackClient.get_input_ports())
        self.populatePortList(self.inputs_model, self.ui.tree_inputs,
                              input_ports)

        # Remove ports, which are no longer present, from recording sources
        all_ports = set((p.client, p.name) for p in output_ports)
        all_ports |= set((p.client, p.name) for p in input_ports)
        self.rec_sources.intersection_update(all_ports)
        self.slot_toggleRecordingSource()

    def makePortTooltip(self, port):
        s = []
        if port.pretty_name:
            s.append(f"<b>Pretty name:</b> <em>{port.pretty_name}</em><br>")
        s.append(f"<b>Port:</b> <tt>{port.client}:{port.name}</tt><br>")
        for i, alias in enumerate(port.aliases, 1):
            s.append(f"<b>Alias {i}:</b> <tt>{alias}</tt><br>")
        s.append(f"<b>UUID:</b> <tt>{port.uuid}</tt>")
        return "<small>{}</small>".format("\n".join(s))

    def populatePortList(self, model, tv, ports):
        tv.setModel(model)
        root = model.invisibleRootItem()

        portsdict = {}
        for port in ports:
            if port.client not in portsdict:
                portsdict[port.client] = []
            portsdict[port.client].append(port)

        for client in humansorted(portsdict):
            clientitem = QStandardItem(client)

            for port in humansorted(portsdict[client],
                                    key=attrgetter("group", "order", "name")):
                portspec = (port.client, port.name)
                if port.pretty_name:
                    label = "%s (%s)" % (port.pretty_name, port.name)
                else:
                    label = port.name

                portitem = QStandardItem(label)
                portitem.setData(portspec)
                portitem.setCheckable(True)
                portitem.setUserTristate(False)
                # Check box toggling is done in the treeview clicked handler "on_port_clicked"
                portitem.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

                portitem.setToolTip(self.makePortTooltip(port))

                if portspec in self.rec_sources:
                    portitem.setCheckState(2)

                clientitem.appendRow(portitem)

            root.appendRow(clientitem)

        tv.expandAll()

    def createUi(self):
        # -------------------------------------------------------------
        # Set-up GUI stuff

        for i in range(self.ui.cb_buffer_size.count()):
            if int(self.ui.cb_buffer_size.itemText(i)) == self.fBufferSize:
                self.ui.cb_buffer_size.setCurrentIndex(i)
                break
        else:
            self.ui.cb_buffer_size.addItem(str(self.fBufferSize))
            self.ui.cb_buffer_size.setCurrentIndex(
                self.ui.cb_buffer_size.count() - 1)

        self.populateFileFormats()
        self.populateSampleFormats()

        self.ui.rb_stereo.setChecked(True)
        self.ui.te_end.setTime(QTime(0, 3, 0))
        self.ui.progressBar.setFormat("")
        self.ui.progressBar.setMinimum(0)
        self.ui.progressBar.setMaximum(1)
        self.ui.progressBar.setValue(0)

        self.ui.b_render.setIcon(get_icon("media-record"))
        self.ui.b_stop.setIcon(get_icon("media-playback-stop"))
        self.ui.b_close.setIcon(get_icon("window-close"))
        self.ui.b_open.setIcon(get_icon("document-open"))
        self.ui.b_stop.setVisible(False)
        self.ui.le_folder.setText(expanduser("~"))

        # -------------------------------------------------------------
        # Set-up connections

        self.ui.b_render.clicked.connect(self.slot_renderStart)
        self.ui.b_stop.clicked.connect(self.slot_renderStop)
        self.ui.b_open.clicked.connect(self.slot_getAndSetPath)
        self.ui.b_now_start.clicked.connect(self.slot_setStartNow)
        self.ui.b_now_end.clicked.connect(self.slot_setEndNow)
        self.ui.te_start.timeChanged.connect(self.slot_updateStartTime)
        self.ui.te_end.timeChanged.connect(self.slot_updateEndTime)
        self.ui.group_time.clicked.connect(self.slot_transportChecked)
        self.ui.rb_source_default.toggled.connect(
            self.slot_toggleRecordingSource)
        self.ui.rb_source_manual.toggled.connect(
            self.slot_toggleRecordingSource)
        self.ui.rb_source_selected.toggled.connect(
            self.slot_toggleRecordingSource)
        self.fTimer.timeout.connect(self.slot_updateProgressbar)

        for tv in (self.ui.tree_outputs, self.ui.tree_inputs):
            menu = QMenu()
            menu.addAction(get_icon("expand-all"), self.tr("E&xpand all"),
                           tv.expandAll)
            menu.addAction(get_icon("collapse-all"), self.tr("&Collapse all"),
                           tv.collapseAll)
            menu.addSeparator()
            menu.addAction(
                get_icon("list-select-all"),
                self.tr("&Select all in group"),
                partial(self.on_select_port_group, tv),
            )
            menu.addAction(
                get_icon("list-select-none"),
                self.tr("&Unselect all in group"),
                partial(self.on_select_port_group, tv, enable=False),
            )
            menu.addSeparator()
            if tv is self.ui.tree_outputs:
                menu.addAction(
                    get_icon("select-none"),
                    self.tr("Unselect all &outputs"),
                    partial(self.on_clear_all_ports, tv),
                )
            else:
                menu.addAction(
                    get_icon("select-none"),
                    self.tr("Unselect all &inputs"),
                    partial(self.on_clear_all_ports, tv),
                )

            tv.setContextMenuPolicy(Qt.CustomContextMenu)
            tv.customContextMenuRequested.connect(
                partial(self.on_port_menu, treeview=tv, menu=menu))
            tv.clicked.connect(self.on_port_clicked)

    def enable_port(self, item, enable=True):
        item.setCheckState(2 if enable else 0)
        port = item.data()
        if enable:
            self.rec_sources.add(port)
        else:
            self.rec_sources.discard(port)

    def on_port_menu(self, pos, treeview=None, menu=None):
        if treeview and menu:
            menu.popup(treeview.viewport().mapToGlobal(pos))

    def foreach_item(self, model, parent, func, leaves_only=True):
        for row in range(model.rowCount(parent)):
            index = model.index(row, 0, parent)
            is_leaf = not model.hasChildren(index)

            if is_leaf or not leaves_only:
                func(model.itemFromIndex(index))

            if not is_leaf:
                self.foreach_item(model, index, func)

    def on_clear_all_ports(self, treeview):
        self.foreach_item(treeview.model(), QModelIndex(),
                          partial(self.enable_port, enable=False))
        self.checkRecordEnable()

    def on_port_clicked(self, index):
        model = index.model()
        item = model.itemFromIndex(index)
        if not model.hasChildren(index):
            self.enable_port(item, not item.checkState())
            self.checkRecordEnable()

    def on_select_port_group(self, treeview, enable=True):
        index = treeview.currentIndex()
        model = index.model()

        if not model.hasChildren(index):
            index = index.parent()

        self.foreach_item(model, index, partial(self.enable_port,
                                                enable=enable))
        self.checkRecordEnable()

    @Slot()
    def slot_renderStart(self):
        if not exists(self.ui.le_folder.text()):
            QMessageBox.warning(
                self,
                self.tr("Warning"),
                self.
                tr("The selected directory does not exist. Please choose a valid one."
                   ),
            )
            return

        timeStart = self.ui.te_start.time()
        timeEnd = self.ui.te_end.time()
        minTime = (timeStart.hour() * 3600) + (timeStart.minute() *
                                               60) + (timeStart.second())
        maxTime = (timeEnd.hour() * 3600) + (timeEnd.minute() *
                                             60) + (timeEnd.second())

        newBufferSize = int(self.ui.cb_buffer_size.currentText())
        useTransport = self.ui.group_time.isChecked()

        self.fFreewheel = self.ui.rb_freewheel.isChecked()
        self.fLastTime = -1
        self.fMaxTime = maxTime

        if self.fFreewheel:
            self.fTimer.setInterval(100)
        else:
            self.fTimer.setInterval(500)

        self.ui.group_render.setEnabled(False)
        self.ui.group_time.setEnabled(False)
        self.ui.group_encoding.setEnabled(False)
        self.ui.b_render.setVisible(False)
        self.ui.b_stop.setVisible(True)
        self.ui.b_close.setEnabled(False)

        if useTransport:
            self.ui.progressBar.setFormat("%p%")
            self.ui.progressBar.setMinimum(minTime)
            self.ui.progressBar.setMaximum(maxTime)
            self.ui.progressBar.setValue(minTime)
        else:
            self.ui.progressBar.setFormat("")
            self.ui.progressBar.setMinimum(0)
            self.ui.progressBar.setMaximum(0)
            self.ui.progressBar.setValue(0)

        self.ui.progressBar.update()

        arguments = []

        # JACK client name
        arguments.append("-jn")
        arguments.append(self.fJackName)

        # Filename prefix
        arguments.append("-fp")
        arguments.append(self.ui.le_prefix.text())

        # File format
        arguments.append("-f")
        arguments.append(self.ui.cb_format.currentText())

        # Sanple format (bit depth, int/float)
        arguments.append("-b")
        arguments.append(self.ui.cb_depth.currentData())

        # Channels
        arguments.append("-c")
        if self.ui.rb_mono.isChecked():
            arguments.append("1")
        elif self.ui.rb_stereo.isChecked():
            arguments.append("2")
        else:
            arguments.append(str(self.ui.sb_channels.value()))

        # Recording sources
        if self.ui.rb_source_manual.isChecked():
            arguments.append("-mc")
        elif self.ui.rb_source_selected.isChecked():
            for client, port in self.rec_sources:
                arguments.append("-p")
                arguments.append("{}:{}".format(client, port))

        # Controlled only by freewheel
        if self.fFreewheel:
            arguments.append("-jf")

        # Controlled by transport
        elif useTransport:
            arguments.append("-jt")

        # Silent mode
        arguments.append("--daemon")

        # Extra arguments
        extra_args = self.ui.le_extra_args.text().strip()

        if extra_args:
            arg_list = shlex.split(extra_args)
            arguments.extend(arg_list)

        # Change current directory
        os.chdir(self.ui.le_folder.text())

        if newBufferSize != self.fJackClient.get_buffer_size():
            log.info("Buffer size changed before render.")
            self.fJackClient.set_buffer_size(newBufferSize)

        if useTransport:
            if self.fJackClient.transport_running():
                # rolling or starting
                self.fJackClient.transport_stop()

            self.fJackClient.transport_locate(minTime * self.fSampleRate)

        log.debug("jack_capture command line args: %r", arguments)
        self.fProcess.start(gJackCapturePath, arguments)
        status = self.fProcess.waitForStarted()

        if not status:
            self.fProcess.close()
            log.error("Could not start jack_capture.")
            return

        if self.fFreewheel:
            log.info("Rendering in freewheel mode.")
            sleep(1)
            self.fJackClient.set_freewheel(True)

        if useTransport:
            log.info("Rendering using JACK transport.")
            self.fTimer.start()
            self.fJackClient.transport_start()

    @Slot()
    def slot_renderStop(self):
        useTransport = self.ui.group_time.isChecked()

        if useTransport:
            self.fJackClient.transport_stop()

        if self.fFreewheel:
            self.fJackClient.set_freewheel(False)
            sleep(1)

        self.fProcess.terminate()
        # self.fProcess.waitForFinished(5000)

        if useTransport:
            self.fTimer.stop()

        self.ui.group_render.setEnabled(True)
        self.ui.group_time.setEnabled(True)
        self.ui.group_encoding.setEnabled(True)
        self.ui.b_render.setVisible(True)
        self.ui.b_stop.setVisible(False)
        self.ui.b_close.setEnabled(True)

        self.ui.progressBar.setFormat("")
        self.ui.progressBar.setMinimum(0)
        self.ui.progressBar.setMaximum(1)
        self.ui.progressBar.setValue(0)
        self.ui.progressBar.update()

        # Restore buffer size
        newBufferSize = self.fJackClient.get_buffer_size()

        if newBufferSize != self.fBufferSize:
            self.fJackClient.set_buffer_size(newBufferSize)

    @Slot()
    def slot_getAndSetPath(self):
        new_path = QFileDialog.getExistingDirectory(self, self.tr("Set Path"),
                                                    self.ui.le_folder.text(),
                                                    QFileDialog.ShowDirsOnly)

        if new_path:
            self.ui.le_folder.setText(new_path)

    @Slot()
    def slot_setStartNow(self):
        time = self.fJackClient.transport_frame() // self.fSampleRate
        secs = time % 60
        mins = int(time / 60) % 60
        hrs = int(time / 3600) % 60
        self.ui.te_start.setTime(QTime(hrs, mins, secs))

    @Slot()
    def slot_setEndNow(self):
        time = self.fJackClient.transport_frame() // self.fSampleRate
        secs = time % 60
        mins = int(time / 60) % 60
        hrs = int(time / 3600) % 60
        self.ui.te_end.setTime(QTime(hrs, mins, secs))

    @Slot(QTime)
    def slot_updateStartTime(self, time):
        if time >= self.ui.te_end.time():
            self.ui.te_end.setTime(time)
            renderEnabled = False
        else:
            renderEnabled = True

        if self.ui.group_time.isChecked():
            self.ui.b_render.setEnabled(renderEnabled)

    @Slot(QTime)
    def slot_updateEndTime(self, time):
        if time <= self.ui.te_start.time():
            self.ui.te_start.setTime(time)
            renderEnabled = False
        else:
            renderEnabled = True

        if self.ui.group_time.isChecked():
            self.ui.b_render.setEnabled(renderEnabled)

    @Slot(bool)
    def slot_toggleRecordingSource(self, dummy=None):
        enabled = self.ui.rb_source_selected.isChecked()
        self.ui.tree_outputs.setEnabled(enabled)
        self.ui.tree_inputs.setEnabled(enabled)
        self.checkRecordEnable()

    @Slot(bool)
    def slot_transportChecked(self, dummy=None):
        self.checkRecordEnable()

    @Slot()
    def slot_updateProgressbar(self):
        time = self.fJackClient.transport_frame() / self.fSampleRate
        self.ui.progressBar.setValue(time)

        if time > self.fMaxTime or (self.fLastTime > time
                                    and not self.fFreewheel):
            self.slot_renderStop()

        self.fLastTime = time

    def checkRecordEnable(self):
        enable = True

        if self.ui.rb_source_selected.isChecked() and not self.rec_sources:
            enable = False

        if self.ui.group_time.isChecked(
        ) and self.ui.te_end.time() <= self.ui.te_start.time():
            enable = False

        self.ui.b_render.setEnabled(enable)
        log.debug("Recording sources: %s", ", ".join(
            ("%s:%s" % (c, p) for c, p in self.rec_sources)))

    def saveSettings(self):
        settings = QSettings(ORGANIZATION, PROGRAM)

        if self.ui.rb_mono.isChecked():
            channels = 1
        elif self.ui.rb_stereo.isChecked():
            channels = 2
        else:
            channels = self.ui.sb_channels.value()

        settings.setValue("Geometry", self.saveGeometry())
        settings.setValue("OutputFolder", self.ui.le_folder.text())
        settings.setValue("FilenamePrefix", self.ui.le_prefix.text())
        settings.setValue("EncodingFormat", self.ui.cb_format.currentText())
        settings.setValue("EncodingDepth", self.ui.cb_depth.currentData())
        settings.setValue("EncodingChannels", channels)
        settings.setValue("UseTransport", self.ui.group_time.isChecked())
        settings.setValue("StartTime", self.ui.te_start.time())
        settings.setValue("EndTime", self.ui.te_end.time())
        settings.setValue("ExtraArgs", self.ui.le_extra_args.text().strip())

        if self.ui.rb_source_default.isChecked():
            settings.setValue("RecordingSource", 0)
        elif self.ui.rb_source_manual.isChecked():
            settings.setValue("RecordingSource", 1)
        elif self.ui.rb_source_selected.isChecked():
            settings.setValue("RecordingSource", 2)

        settings.beginWriteArray("Sources")
        for i, (client, port) in enumerate(self.rec_sources):
            settings.setArrayIndex(i)
            settings.setValue("Client", client)
            settings.setValue("Port", port)
        settings.endArray()

    def loadSettings(self):
        settings = QSettings(ORGANIZATION, PROGRAM)

        self.restoreGeometry(settings.value("Geometry", b""))

        outputFolder = settings.value("OutputFolder", get_user_dir("MUSIC"))

        if isdir(outputFolder):
            self.ui.le_folder.setText(outputFolder)

        self.ui.le_prefix.setText(
            settings.value("FilenamePrefix", "jack_capture_"))

        encFormat = settings.value("EncodingFormat", "wav", type=str)

        for i in range(self.ui.cb_format.count()):
            if self.ui.cb_format.itemText(i) == encFormat:
                self.ui.cb_format.setCurrentIndex(i)
                break

        encDepth = settings.value("EncodingDepth", "FLOAT", type=str)

        for i in range(self.ui.cb_depth.count()):
            if self.ui.cb_depth.itemData(i) == encDepth:
                self.ui.cb_depth.setCurrentIndex(i)
                break

        encChannels = settings.value("EncodingChannels", 2, type=int)

        if encChannels == 1:
            self.ui.rb_mono.setChecked(True)
        elif encChannels == 2:
            self.ui.rb_stereo.setChecked(True)
        else:
            self.ui.rb_outro.setChecked(True)
            self.ui.sb_channels.setValue(encChannels)

        recSource = settings.value("RecordingSource", 0, type=int)

        if recSource == 1:
            self.ui.rb_source_manual.setChecked(True)
        elif recSource == 2:
            self.ui.rb_source_selected.setChecked(True)
        else:
            self.ui.rb_source_default.setChecked(True)

        self.ui.group_time.setChecked(
            settings.value("UseTransport", False, type=bool))
        self.ui.te_start.setTime(
            settings.value("StartTime", self.ui.te_start.time(), type=QTime))
        self.ui.te_end.setTime(
            settings.value("EndTime", self.ui.te_end.time(), type=QTime))

        self.ui.le_extra_args.setText(settings.value("ExtraArgs", "",
                                                     type=str))

        size = settings.beginReadArray("Sources")
        for i in range(size):
            settings.setArrayIndex(i)
            client = settings.value("Client", type=str)
            port = settings.value("Port", type=str)
            if client and port:
                self.rec_sources.add((client, port))
        settings.endArray()

    def closeEvent(self, event):
        self.saveSettings()
        self.fJackClient.close()
        QDialog.closeEvent(self, event)

    def done(self, r):
        QDialog.done(self, r)
        self.close()
Пример #2
0
class ProcessWorker(QObject):
    """Process worker based on a QProcess for non blocking UI."""

    sig_started = Signal(object)
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._started = False

        self._timer = QTimer()
        self._process = QProcess()
        self._set_environment(environ)

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _get_encoding(self):
        """Return the encoding/codepage to use."""
        enco = 'utf-8'

        #  Currently only cp1252 is allowed?
        if WIN:
            import ctypes
            codepage = to_text_string(ctypes.cdll.kernel32.GetACP())
            # import locale
            # locale.getpreferredencoding()  # Differences?
            enco = 'cp' + codepage
        return enco

    def _set_environment(self, environ):
        """Set the environment on the QProcess."""
        if environ:
            q_environ = self._process.processEnvironment()
            for k, v in environ.items():
                q_environ.insert(k, v)
            self._process.setProcessEnvironment(q_environ)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, self._get_encoding())

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first
                and self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        enco = self._get_encoding()
        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, enco)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, enco)
        result = [stdout.encode(enco), stderr.encode(enco)]

        if PY2:
            stderr = stderr.decode()
        result[-1] = ''

        self._result = result

        if not self._fired:
            self.sig_finished.emit(self, result[0], result[-1])

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def _start(self):
        """Start process."""
        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()

    def terminate(self):
        """Terminate running processes."""
        if self._process.state() == QProcess.Running:
            try:
                self._process.terminate()
            except Exception:
                pass
        self._fired = True

    def start(self):
        """Start worker."""
        if not self._started:
            self.sig_started.emit(self)
            self._started = True
Пример #3
0
class ProcessWorker(QObject):
    """Process worker based on a QProcess for non blocking UI."""

    sig_started = Signal(object)        
    sig_finished = Signal(object, object, object)
    sig_partial = Signal(object, object, object)

    def __init__(self, cmd_list, environ=None):
        """
        Process worker based on a QProcess for non blocking UI.

        Parameters
        ----------
        cmd_list : list of str
            Command line arguments to execute.
        environ : dict
            Process environment,
        """
        super(ProcessWorker, self).__init__()
        self._result = None
        self._cmd_list = cmd_list
        self._fired = False
        self._communicate_first = False
        self._partial_stdout = None
        self._started = False

        self._timer = QTimer()
        self._process = QProcess()
        self._set_environment(environ)

        self._timer.setInterval(150)
        self._timer.timeout.connect(self._communicate)
        self._process.readyReadStandardOutput.connect(self._partial)

    def _get_encoding(self):
        """Return the encoding/codepage to use."""
        enco = 'utf-8'

        #  Currently only cp1252 is allowed?
        if WIN:
            import ctypes
            codepage = to_text_string(ctypes.cdll.kernel32.GetACP())
            # import locale
            # locale.getpreferredencoding()  # Differences?
            enco = 'cp' + codepage
        return enco

    def _set_environment(self, environ):
        """Set the environment on the QProcess."""
        if environ:
            q_environ = self._process.processEnvironment()
            for k, v in environ.items():
                q_environ.insert(k, v)
            self._process.setProcessEnvironment(q_environ)

    def _partial(self):
        """Callback for partial output."""
        raw_stdout = self._process.readAllStandardOutput()
        stdout = handle_qbytearray(raw_stdout, self._get_encoding())

        if self._partial_stdout is None:
            self._partial_stdout = stdout
        else:
            self._partial_stdout += stdout

        self.sig_partial.emit(self, stdout, None)

    def _communicate(self):
        """Callback for communicate."""
        if (not self._communicate_first and
                self._process.state() == QProcess.NotRunning):
            self.communicate()
        elif self._fired:
            self._timer.stop()

    def communicate(self):
        """Retrieve information."""
        self._communicate_first = True
        self._process.waitForFinished()

        enco = self._get_encoding()
        if self._partial_stdout is None:
            raw_stdout = self._process.readAllStandardOutput()
            stdout = handle_qbytearray(raw_stdout, enco)
        else:
            stdout = self._partial_stdout

        raw_stderr = self._process.readAllStandardError()
        stderr = handle_qbytearray(raw_stderr, enco)
        result = [stdout.encode(enco), stderr.encode(enco)]

        if PY2:
            stderr = stderr.decode()
        result[-1] = ''

        self._result = result

        if not self._fired:
            self.sig_finished.emit(self, result[0], result[-1])

        self._fired = True

        return result

    def close(self):
        """Close the running process."""
        self._process.close()

    def is_finished(self):
        """Return True if worker has finished processing."""
        return self._process.state() == QProcess.NotRunning and self._fired

    def _start(self):
        """Start process."""
        if not self._fired:
            self._partial_ouput = None
            self._process.start(self._cmd_list[0], self._cmd_list[1:])
            self._timer.start()

    def terminate(self):
        """Terminate running processes."""
        if self._process.state() == QProcess.Running:
            try:
                self._process.terminate()
            except Exception:
                pass
        self._fired = True

    def start(self):
        """Start worker."""
        if not self._started:
            self.sig_started.emit(self)
            self._started = True