forked from quickshell/quickshell
feat: add Process type
This commit is contained in:
parent
cd2343e57d
commit
3f0bd20852
|
@ -1,5 +1,6 @@
|
||||||
qt_add_library(quickshell-io STATIC
|
qt_add_library(quickshell-io STATIC
|
||||||
datastream.cpp
|
datastream.cpp
|
||||||
|
process.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if (SOCKETS)
|
if (SOCKETS)
|
||||||
|
|
|
@ -3,5 +3,6 @@ description = "Io types"
|
||||||
headers = [
|
headers = [
|
||||||
"datastream.hpp",
|
"datastream.hpp",
|
||||||
"socket.hpp",
|
"socket.hpp",
|
||||||
|
"process.hpp",
|
||||||
]
|
]
|
||||||
-----
|
-----
|
||||||
|
|
159
src/io/process.cpp
Normal file
159
src/io/process.cpp
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
#include "process.hpp"
|
||||||
|
#include <csignal> // NOLINT
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qlist.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qprocess.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
#include "datastream.hpp"
|
||||||
|
|
||||||
|
bool Process::isRunning() const { return this->process != nullptr; }
|
||||||
|
|
||||||
|
void Process::setRunning(bool running) {
|
||||||
|
this->targetRunning = running;
|
||||||
|
if (running) this->startProcessIfReady();
|
||||||
|
else if (this->isRunning()) this->process->terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant Process::pid() const {
|
||||||
|
if (this->process == nullptr) return QVariant();
|
||||||
|
return QVariant::fromValue(this->process->processId());
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> Process::command() const { return this->mCommand; }
|
||||||
|
|
||||||
|
void Process::setCommand(QList<QString> command) {
|
||||||
|
if (this->mCommand == command) return;
|
||||||
|
this->mCommand = std::move(command);
|
||||||
|
emit this->commandChanged();
|
||||||
|
|
||||||
|
this->startProcessIfReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataStreamParser* Process::stdoutParser() const { return this->mStdoutParser; }
|
||||||
|
|
||||||
|
void Process::setStdoutParser(DataStreamParser* parser) {
|
||||||
|
if (parser == this->mStdoutParser) return;
|
||||||
|
|
||||||
|
if (this->mStdoutParser != nullptr) {
|
||||||
|
QObject::disconnect(this->mStdoutParser, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mStdoutParser = parser;
|
||||||
|
|
||||||
|
if (parser != nullptr) {
|
||||||
|
QObject::connect(parser, &QObject::destroyed, this, &Process::onStdoutParserDestroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->stdoutParserChanged();
|
||||||
|
|
||||||
|
if (parser != nullptr && !this->stdoutBuffer.isEmpty()) {
|
||||||
|
parser->parseBytes(this->stdoutBuffer, this->stdoutBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onStdoutParserDestroyed() {
|
||||||
|
this->mStdoutParser = nullptr;
|
||||||
|
emit this->stdoutParserChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
DataStreamParser* Process::stderrParser() const { return this->mStderrParser; }
|
||||||
|
|
||||||
|
void Process::setStderrParser(DataStreamParser* parser) {
|
||||||
|
if (parser == this->mStderrParser) return;
|
||||||
|
|
||||||
|
if (this->mStderrParser != nullptr) {
|
||||||
|
QObject::disconnect(this->mStderrParser, nullptr, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mStderrParser = parser;
|
||||||
|
|
||||||
|
if (parser != nullptr) {
|
||||||
|
QObject::connect(parser, &QObject::destroyed, this, &Process::onStderrParserDestroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit this->stderrParserChanged();
|
||||||
|
|
||||||
|
if (parser != nullptr && !this->stderrBuffer.isEmpty()) {
|
||||||
|
parser->parseBytes(this->stderrBuffer, this->stderrBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onStderrParserDestroyed() {
|
||||||
|
this->mStderrParser = nullptr;
|
||||||
|
emit this->stderrParserChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::startProcessIfReady() {
|
||||||
|
if (this->process != nullptr || !this->targetRunning || this->mCommand.isEmpty()) return;
|
||||||
|
this->targetRunning = false;
|
||||||
|
|
||||||
|
auto& cmd = this->mCommand.first();
|
||||||
|
auto args = this->mCommand.sliced(1);
|
||||||
|
|
||||||
|
this->process = new QProcess(this);
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
QObject::connect(this->process, &QProcess::started, this, &Process::onStarted);
|
||||||
|
QObject::connect(this->process, &QProcess::finished, this, &Process::onFinished);
|
||||||
|
QObject::connect(this->process, &QProcess::errorOccurred, this, &Process::onErrorOccurred);
|
||||||
|
QObject::connect(this->process, &QProcess::readyReadStandardOutput, this, &Process::onStdoutReadyRead);
|
||||||
|
QObject::connect(this->process, &QProcess::readyReadStandardError, this, &Process::onStderrReadyRead);
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
this->stdoutBuffer.clear();
|
||||||
|
this->stderrBuffer.clear();
|
||||||
|
|
||||||
|
this->process->start(cmd, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onStarted() {
|
||||||
|
emit this->started();
|
||||||
|
emit this->runningChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus) {
|
||||||
|
this->process->deleteLater();
|
||||||
|
this->process = nullptr;
|
||||||
|
this->stdoutBuffer.clear();
|
||||||
|
this->stderrBuffer.clear();
|
||||||
|
|
||||||
|
emit this->exited(exitCode, exitStatus);
|
||||||
|
emit this->runningChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onErrorOccurred(QProcess::ProcessError error) {
|
||||||
|
if (error == QProcess::FailedToStart) { // other cases should be covered by other events
|
||||||
|
qWarning() << "Process failed to start, likely because the binary could not be found. Command:"
|
||||||
|
<< this->mCommand;
|
||||||
|
this->process->deleteLater();
|
||||||
|
this->process = nullptr;
|
||||||
|
emit this->runningChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onStdoutReadyRead() {
|
||||||
|
if (this->mStdoutParser == nullptr) return;
|
||||||
|
auto buf = this->process->readAllStandardOutput();
|
||||||
|
this->mStdoutParser->parseBytes(buf, this->stdoutBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::onStderrReadyRead() {
|
||||||
|
if (this->mStderrParser == nullptr) return;
|
||||||
|
auto buf = this->process->readAllStandardError();
|
||||||
|
this->mStderrParser->parseBytes(buf, this->stderrBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::signal(qint32 signal) {
|
||||||
|
if (this->process == nullptr) return;
|
||||||
|
kill(static_cast<qint32>(this->process->processId()), signal); // NOLINT
|
||||||
|
}
|
||||||
|
|
||||||
|
void Process::write(const QString& data) {
|
||||||
|
if (this->process == nullptr) return;
|
||||||
|
this->process->write(data.toUtf8());
|
||||||
|
}
|
114
src/io/process.hpp
Normal file
114
src/io/process.hpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <qobject.h>
|
||||||
|
#include <qprocess.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
#include <qtypes.h>
|
||||||
|
#include <qvariant.h>
|
||||||
|
|
||||||
|
#include "datastream.hpp"
|
||||||
|
|
||||||
|
///! Child process.
|
||||||
|
/// #### Example
|
||||||
|
/// ```qml
|
||||||
|
/// Process {
|
||||||
|
/// running: true
|
||||||
|
/// command: [ "some-command", "arg" ]
|
||||||
|
/// stdout: SplitParser {
|
||||||
|
/// onRead: data => console.log(`line read: ${data}`)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
class Process: public QObject {
|
||||||
|
Q_OBJECT;
|
||||||
|
// clang-format off
|
||||||
|
/// If the process is currently running. Defaults to false.
|
||||||
|
///
|
||||||
|
/// Setting this property to true will start the process if command has at least
|
||||||
|
/// one element.
|
||||||
|
/// Setting it to false will send SIGTERM. To immediately kill the process,
|
||||||
|
/// use [signal](#func.signal) with SIGKILL. The process will be killed when
|
||||||
|
/// quickshell dies.
|
||||||
|
///
|
||||||
|
/// If you want to run the process in a loop, use the onRunningChanged signal handler
|
||||||
|
/// to restart the process.
|
||||||
|
/// ```qml
|
||||||
|
/// Process {
|
||||||
|
/// running: true
|
||||||
|
/// onRunningChanged: if (!running) running = true
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged);
|
||||||
|
/// The process ID of the running process or `null` if `running` is false.
|
||||||
|
Q_PROPERTY(QVariant pid READ pid NOTIFY pidChanged);
|
||||||
|
/// The command to execute.
|
||||||
|
///
|
||||||
|
/// If the process is already running changing this property will affect the next
|
||||||
|
/// started process. If the property has been changed after starting a process it will
|
||||||
|
/// return the new value, not the one for the currently running process.
|
||||||
|
///
|
||||||
|
/// > [!INFO] You can use `["sh", "-c", <your command>]` to execute your command with
|
||||||
|
/// > the system shell.
|
||||||
|
Q_PROPERTY(QList<QString> command READ command WRITE setCommand NOTIFY commandChanged);
|
||||||
|
/// The parser for STDOUT. If the parser is null no data will be read.
|
||||||
|
Q_PROPERTY(DataStreamParser* stdout READ stdoutParser WRITE setStdoutParser NOTIFY stdoutParserChanged);
|
||||||
|
/// The parser for STDERR. If the parser is null no data will be read.
|
||||||
|
Q_PROPERTY(DataStreamParser* stderr READ stderrParser WRITE setStderrParser NOTIFY stderrParserChanged);
|
||||||
|
// clang-format on
|
||||||
|
QML_ELEMENT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Process(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
/// Sends a signal to the process if `running` is true, otherwise does nothing.
|
||||||
|
Q_INVOKABLE void signal(qint32 signal);
|
||||||
|
|
||||||
|
/// Writes to the process's STDIN. Does nothing if `running` is false.
|
||||||
|
Q_INVOKABLE void write(const QString& data);
|
||||||
|
|
||||||
|
[[nodiscard]] bool isRunning() const;
|
||||||
|
void setRunning(bool running);
|
||||||
|
|
||||||
|
[[nodiscard]] QVariant pid() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QList<QString> command() const;
|
||||||
|
void setCommand(QList<QString> command);
|
||||||
|
|
||||||
|
[[nodiscard]] DataStreamParser* stdoutParser() const;
|
||||||
|
void setStdoutParser(DataStreamParser* parser);
|
||||||
|
|
||||||
|
[[nodiscard]] DataStreamParser* stderrParser() const;
|
||||||
|
void setStderrParser(DataStreamParser* parser);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void started();
|
||||||
|
void exited(qint32 exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
|
||||||
|
void runningChanged();
|
||||||
|
void pidChanged();
|
||||||
|
void commandChanged();
|
||||||
|
void stdoutParserChanged();
|
||||||
|
void stderrParserChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onStarted();
|
||||||
|
void onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus);
|
||||||
|
void onErrorOccurred(QProcess::ProcessError error);
|
||||||
|
void onStdoutReadyRead();
|
||||||
|
void onStderrReadyRead();
|
||||||
|
void onStdoutParserDestroyed();
|
||||||
|
void onStderrParserDestroyed();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startProcessIfReady();
|
||||||
|
|
||||||
|
QProcess* process = nullptr;
|
||||||
|
QList<QString> mCommand;
|
||||||
|
DataStreamParser* mStdoutParser = nullptr;
|
||||||
|
DataStreamParser* mStderrParser = nullptr;
|
||||||
|
QByteArray stdoutBuffer;
|
||||||
|
QByteArray stderrBuffer;
|
||||||
|
|
||||||
|
bool targetRunning = false;
|
||||||
|
};
|
Loading…
Reference in a new issue