forked from quickshell/quickshell
		
	feat: add Process type
This commit is contained in:
		
							parent
							
								
									cd2343e57d
								
							
						
					
					
						commit
						3f0bd20852
					
				
					 4 changed files with 275 additions and 0 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue