diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index c447c554..0aaf06c5 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -250,10 +250,10 @@ QVariant QuickshellGlobal::env(const QString& variable) { // NOLINT } void QuickshellGlobal::execDetached(QList command) { - QuickshellGlobal::execDetached(ProcessContext(std::move(command))); + QuickshellGlobal::execDetached(qs::io::process::ProcessContext(std::move(command))); } -void QuickshellGlobal::execDetached(const ProcessContext& context) { +void QuickshellGlobal::execDetached(const qs::io::process::ProcessContext& context) { if (context.command.isEmpty()) { qWarning() << "Cannot start process as command is empty."; return; @@ -264,11 +264,7 @@ void QuickshellGlobal::execDetached(const ProcessContext& context) { QProcess process; - qs::core::process::setupProcessEnvironment( - &process, - context.clearEnvironment, - context.environment - ); + qs::io::process::setupProcessEnvironment(&process, context.clearEnvironment, context.environment); if (!context.workingDirectory.isEmpty()) { process.setWorkingDirectory(context.workingDirectory); diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index d5b98447..82442ce3 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -1,7 +1,5 @@ #pragma once -#include - #include #include #include @@ -17,27 +15,10 @@ #include #include +#include "../io/processcore.hpp" +#include "doc.hpp" #include "qmlscreen.hpp" -class ProcessContext { - Q_PROPERTY(QList command MEMBER command); - Q_PROPERTY(QHash environment MEMBER environment); - Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment); - Q_PROPERTY(QString workingDirectory MEMBER workingDirectory); - Q_GADGET; - QML_STRUCTURED_VALUE; - QML_VALUE_TYPE(processContext); - -public: - ProcessContext() = default; - explicit ProcessContext(QList command): command(std::move(command)) {} - - QList command; - QHash environment; - bool clearEnvironment = false; - QString workingDirectory; -}; - ///! Accessor for some options under the Quickshell type. class QuickshellSettings: public QObject { Q_OBJECT; @@ -175,27 +156,11 @@ public: /// Returns the string value of an environment variable or null if it is not set. Q_INVOKABLE QVariant env(const QString& variable); + // MUST be before execDetached(ctx) or the other will be called with a default constructed obj. + QSDOC_HIDE Q_INVOKABLE static void execDetached(QList command); /// Launch a process detached from Quickshell. /// - /// Each command argument is its own string, meaning arguments do - /// not have to be escaped. - /// - /// > [!WARNING] This does not run command in a shell. All arguments to the command - /// > must be in separate values in the list, e.g. `["echo", "hello"]` - /// > and not `["echo hello"]`. - /// > - /// > Additionally, shell scripts must be run by your shell, - /// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script - /// > has a shebang. - /// - /// > [!INFO] You can use `["sh", "-c", ]` to execute your command with - /// > the system shell. - /// - /// This function is equivalent to @@Quickshell.Io.Process.startDetached(). - Q_INVOKABLE static void execDetached(QList command); - /// Launch a process detached from Quickshell. - /// - /// The context parameter is a JS object with the following fields: + /// The context parameter can either be a list of command arguments or a JS object with the following fields: /// - `command`: A list containing the command and all its arguments. See @@Quickshell.Io.Process.command. /// - `environment`: Changes to make to the process environment. See @@Quickshell.Io.Process.environment. /// - `clearEnvironment`: Removes all variables from the environment if true. @@ -213,7 +178,7 @@ public: /// > the system shell. /// /// This function is equivalent to @@Quickshell.Io.Process.startDetached(). - Q_INVOKABLE static void execDetached(const ProcessContext& context); + Q_INVOKABLE static void execDetached(const qs::io::process::ProcessContext& context); /// Returns a string usable for a @@QtQuick.Image.source for a given system icon. /// diff --git a/src/io/process.cpp b/src/io/process.cpp index 43637d42..8aa6c24b 100644 --- a/src/io/process.cpp +++ b/src/io/process.cpp @@ -46,17 +46,7 @@ void Process::setCommand(QList command) { if (this->mCommand == command) return; this->mCommand = std::move(command); - auto& cmd = this->mCommand.first(); - if (cmd.startsWith("file://")) { - cmd = cmd.sliced(7); - } else if (cmd.startsWith("root://")) { - cmd = cmd.sliced(7); - auto& root = EngineGeneration::findObjectGeneration(this)->rootPath; - cmd = root.filePath(cmd.startsWith('/') ? cmd.sliced(1) : cmd); - } - emit this->commandChanged(); - this->startProcessIfReady(); } @@ -82,7 +72,6 @@ void Process::onGlobalWorkingDirectoryChanged() { QHash Process::environment() const { return this->mEnvironment; } void Process::setEnvironment(QHash environment) { - qDebug() << "setEnv" << environment; if (environment == this->mEnvironment) return; this->mEnvironment = std::move(environment); emit this->environmentChanged(); @@ -180,6 +169,14 @@ void Process::startProcessIfReady() { this->targetRunning = false; auto& cmd = this->mCommand.first(); + if (cmd.startsWith("file://")) { + cmd = cmd.sliced(7); + } else if (cmd.startsWith("root://")) { + cmd = cmd.sliced(7); + auto& root = EngineGeneration::findObjectGeneration(this)->rootPath; + cmd = root.filePath(cmd.startsWith('/') ? cmd.sliced(1) : cmd); + } + auto args = this->mCommand.sliced(1); this->process = new QProcess(this); @@ -203,6 +200,25 @@ void Process::startProcessIfReady() { this->process->start(cmd, args); } +void Process::exec(QList command) { + this->exec(qs::io::process::ProcessContext(std::move(command))); +} + +void Process::exec(const qs::io::process::ProcessContext& context) { + this->setRunning(false); + if (context.commandSet) this->setCommand(context.command); + if (context.environmentSet) this->setEnvironment(context.environment); + if (context.clearEnvironmentSet) this->setEnvironmentCleared(context.clearEnvironment); + if (context.workingDirectorySet) this->setWorkingDirectory(context.workingDirectory); + + if (this->mCommand.isEmpty()) { + qmlWarning(this) << "Cannot start process as command is empty."; + return; + } + + this->setRunning(true); +} + void Process::startDetached() { if (this->mCommand.isEmpty()) { qmlWarning(this) << "Cannot start process as command is empty."; @@ -225,7 +241,7 @@ void Process::setupEnvironment(QProcess* process) { process->setWorkingDirectory(this->mWorkingDirectory); } - qs::core::process::setupProcessEnvironment(process, this->mClearEnvironment, this->mEnvironment); + qs::io::process::setupProcessEnvironment(process, this->mClearEnvironment, this->mEnvironment); } void Process::onStarted() { @@ -245,6 +261,8 @@ void Process::onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus) { emit this->exited(exitCode, exitStatus); emit this->runningChanged(); emit this->processIdChanged(); + + this->startProcessIfReady(); // for `running = false; running = true` } void Process::onErrorOccurred(QProcess::ProcessError error) { diff --git a/src/io/process.hpp b/src/io/process.hpp index 2d7e1fd4..c9e983ee 100644 --- a/src/io/process.hpp +++ b/src/io/process.hpp @@ -10,7 +10,9 @@ #include #include +#include "../core/doc.hpp" #include "datastream.hpp" +#include "processcore.hpp" // Needed when compiling with clang musl-libc++. // Default include paths contain macros that cause name collisions. @@ -135,6 +137,40 @@ class Process: public QObject { public: explicit Process(QObject* parent = nullptr); + // MUST be before exec(ctx) or the other will be called with a default constructed obj. + QSDOC_HIDE Q_INVOKABLE void exec(QList command); + /// Launch a process with the given arguments, stopping any currently running process. + /// + /// The context parameter can either be a list of command arguments or a JS object with the following fields: + /// - `command`: A list containing the command and all its arguments. See @@Quickshell.Io.Process.command. + /// - `environment`: Changes to make to the process environment. See @@Quickshell.Io.Process.environment. + /// - `clearEnvironment`: Removes all variables from the environment if true. + /// - `workingDirectory`: The working directory the command should run in. + /// + /// Passed parameters will change the values currently set in the process. + /// + /// > [!WARNING] This does not run command in a shell. All arguments to the command + /// > must be in separate values in the list, e.g. `["echo", "hello"]` + /// > and not `["echo hello"]`. + /// > + /// > Additionally, shell scripts must be run by your shell, + /// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script + /// > has a shebang. + /// + /// > [!INFO] You can use `["sh", "-c", ]` to execute your command with + /// > the system shell. + /// + /// Calling this function is equivalent to running: + /// ```qml + /// process.running = false; + /// process.command = ... + /// process.environment = ... + /// process.clearEnvironment = ... + /// process.workingDirectory = ... + /// process.running = true; + /// ``` + Q_INVOKABLE void exec(const qs::io::process::ProcessContext& context); + /// Sends a signal to the process if @@running is true, otherwise does nothing. Q_INVOKABLE void signal(qint32 signal); diff --git a/src/io/processcore.cpp b/src/io/processcore.cpp index 572045e4..8b5e80eb 100644 --- a/src/io/processcore.cpp +++ b/src/io/processcore.cpp @@ -7,7 +7,7 @@ #include "../core/common.hpp" -namespace qs::core::process { +namespace qs::io::process { void setupProcessEnvironment( QProcess* process, @@ -34,4 +34,4 @@ void setupProcessEnvironment( process->setProcessEnvironment(env); } -} // namespace qs::core::process +} // namespace qs::io::process diff --git a/src/io/processcore.hpp b/src/io/processcore.hpp index fe8bda7b..c74f6fbc 100644 --- a/src/io/processcore.hpp +++ b/src/io/processcore.hpp @@ -1,11 +1,60 @@ #pragma once +#include + #include #include +#include #include +#include #include -namespace qs::core::process { +namespace qs::io::process { + +class ProcessContext { + Q_PROPERTY(QList command MEMBER command WRITE setCommand); + Q_PROPERTY(QHash environment MEMBER environment WRITE setEnvironment); + Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment); + Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory); + Q_GADGET; + QML_STRUCTURED_VALUE; + QML_VALUE_TYPE(processContext); + +public: + ProcessContext() = default; + // Making this a Q_INVOKABLE does not work with QML_STRUCTURED_VALUe in Qt 6.9. + explicit ProcessContext(QList command): command(std::move(command)), commandSet(true) {} + + void setCommand(QList command) { + this->command = std::move(command); + this->commandSet = true; + } + + void setEnvironment(QHash environment) { + this->environment = std::move(environment); + this->environmentSet = true; + } + + void setClearEnvironment(bool clearEnvironment) { + this->clearEnvironment = clearEnvironment; + this->clearEnvironmentSet = true; + } + + void setWorkingDirectory(QString workingDirectory) { + this->workingDirectory = std::move(workingDirectory); + this->workingDirectorySet = true; + } + + QList command; + QHash environment; + bool clearEnvironment = false; + QString workingDirectory; + + bool commandSet : 1 = false; + bool environmentSet : 1 = false; + bool clearEnvironmentSet : 1 = false; + bool workingDirectorySet : 1 = false; +}; void setupProcessEnvironment( QProcess* process, @@ -13,4 +62,4 @@ void setupProcessEnvironment( const QHash& envChanges ); -} +} // namespace qs::io::process