Skip to content

Commit 98d09b5

Browse files
committed
io/process: add Process.exec()
1 parent 8fc3e1c commit 98d09b5

File tree

6 files changed

+128
-64
lines changed

6 files changed

+128
-64
lines changed

src/core/qmlglobal.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,10 @@ QVariant QuickshellGlobal::env(const QString& variable) { // NOLINT
250250
}
251251

252252
void QuickshellGlobal::execDetached(QList<QString> command) {
253-
QuickshellGlobal::execDetached(ProcessContext(std::move(command)));
253+
QuickshellGlobal::execDetached(qs::io::process::ProcessContext(std::move(command)));
254254
}
255255

256-
void QuickshellGlobal::execDetached(const ProcessContext& context) {
256+
void QuickshellGlobal::execDetached(const qs::io::process::ProcessContext& context) {
257257
if (context.command.isEmpty()) {
258258
qWarning() << "Cannot start process as command is empty.";
259259
return;
@@ -264,11 +264,7 @@ void QuickshellGlobal::execDetached(const ProcessContext& context) {
264264

265265
QProcess process;
266266

267-
qs::core::process::setupProcessEnvironment(
268-
&process,
269-
context.clearEnvironment,
270-
context.environment
271-
);
267+
qs::io::process::setupProcessEnvironment(&process, context.clearEnvironment, context.environment);
272268

273269
if (!context.workingDirectory.isEmpty()) {
274270
process.setWorkingDirectory(context.workingDirectory);

src/core/qmlglobal.hpp

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
#pragma once
22

3-
#include <utility>
4-
53
#include <qclipboard.h>
64
#include <qcontainerfwd.h>
75
#include <qhash.h>
@@ -17,27 +15,10 @@
1715
#include <qvariant.h>
1816
#include <qwindowdefs.h>
1917

18+
#include "../io/processcore.hpp"
19+
#include "doc.hpp"
2020
#include "qmlscreen.hpp"
2121

22-
class ProcessContext {
23-
Q_PROPERTY(QList<QString> command MEMBER command);
24-
Q_PROPERTY(QHash<QString, QVariant> environment MEMBER environment);
25-
Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment);
26-
Q_PROPERTY(QString workingDirectory MEMBER workingDirectory);
27-
Q_GADGET;
28-
QML_STRUCTURED_VALUE;
29-
QML_VALUE_TYPE(processContext);
30-
31-
public:
32-
ProcessContext() = default;
33-
explicit ProcessContext(QList<QString> command): command(std::move(command)) {}
34-
35-
QList<QString> command;
36-
QHash<QString, QVariant> environment;
37-
bool clearEnvironment = false;
38-
QString workingDirectory;
39-
};
40-
4122
///! Accessor for some options under the Quickshell type.
4223
class QuickshellSettings: public QObject {
4324
Q_OBJECT;
@@ -175,27 +156,11 @@ class QuickshellGlobal: public QObject {
175156
/// Returns the string value of an environment variable or null if it is not set.
176157
Q_INVOKABLE QVariant env(const QString& variable);
177158

159+
// MUST be before execDetached(ctx) or the other will be called with a default constructed obj.
160+
QSDOC_HIDE Q_INVOKABLE static void execDetached(QList<QString> command);
178161
/// Launch a process detached from Quickshell.
179162
///
180-
/// Each command argument is its own string, meaning arguments do
181-
/// not have to be escaped.
182-
///
183-
/// > [!WARNING] This does not run command in a shell. All arguments to the command
184-
/// > must be in separate values in the list, e.g. `["echo", "hello"]`
185-
/// > and not `["echo hello"]`.
186-
/// >
187-
/// > Additionally, shell scripts must be run by your shell,
188-
/// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script
189-
/// > has a shebang.
190-
///
191-
/// > [!INFO] You can use `["sh", "-c", <your command>]` to execute your command with
192-
/// > the system shell.
193-
///
194-
/// This function is equivalent to @@Quickshell.Io.Process.startDetached().
195-
Q_INVOKABLE static void execDetached(QList<QString> command);
196-
/// Launch a process detached from Quickshell.
197-
///
198-
/// The context parameter is a JS object with the following fields:
163+
/// The context parameter can either be a list of command arguments or a JS object with the following fields:
199164
/// - `command`: A list containing the command and all its arguments. See @@Quickshell.Io.Process.command.
200165
/// - `environment`: Changes to make to the process environment. See @@Quickshell.Io.Process.environment.
201166
/// - `clearEnvironment`: Removes all variables from the environment if true.
@@ -213,7 +178,7 @@ class QuickshellGlobal: public QObject {
213178
/// > the system shell.
214179
///
215180
/// This function is equivalent to @@Quickshell.Io.Process.startDetached().
216-
Q_INVOKABLE static void execDetached(const ProcessContext& context);
181+
Q_INVOKABLE static void execDetached(const qs::io::process::ProcessContext& context);
217182

218183
/// Returns a string usable for a @@QtQuick.Image.source for a given system icon.
219184
///

src/io/process.cpp

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,7 @@ void Process::setCommand(QList<QString> command) {
4646
if (this->mCommand == command) return;
4747
this->mCommand = std::move(command);
4848

49-
auto& cmd = this->mCommand.first();
50-
if (cmd.startsWith("file://")) {
51-
cmd = cmd.sliced(7);
52-
} else if (cmd.startsWith("root://")) {
53-
cmd = cmd.sliced(7);
54-
auto& root = EngineGeneration::findObjectGeneration(this)->rootPath;
55-
cmd = root.filePath(cmd.startsWith('/') ? cmd.sliced(1) : cmd);
56-
}
57-
5849
emit this->commandChanged();
59-
6050
this->startProcessIfReady();
6151
}
6252

@@ -82,7 +72,6 @@ void Process::onGlobalWorkingDirectoryChanged() {
8272
QHash<QString, QVariant> Process::environment() const { return this->mEnvironment; }
8373

8474
void Process::setEnvironment(QHash<QString, QVariant> environment) {
85-
qDebug() << "setEnv" << environment;
8675
if (environment == this->mEnvironment) return;
8776
this->mEnvironment = std::move(environment);
8877
emit this->environmentChanged();
@@ -180,6 +169,14 @@ void Process::startProcessIfReady() {
180169
this->targetRunning = false;
181170

182171
auto& cmd = this->mCommand.first();
172+
if (cmd.startsWith("file://")) {
173+
cmd = cmd.sliced(7);
174+
} else if (cmd.startsWith("root://")) {
175+
cmd = cmd.sliced(7);
176+
auto& root = EngineGeneration::findObjectGeneration(this)->rootPath;
177+
cmd = root.filePath(cmd.startsWith('/') ? cmd.sliced(1) : cmd);
178+
}
179+
183180
auto args = this->mCommand.sliced(1);
184181

185182
this->process = new QProcess(this);
@@ -203,6 +200,25 @@ void Process::startProcessIfReady() {
203200
this->process->start(cmd, args);
204201
}
205202

203+
void Process::exec(QList<QString> command) {
204+
this->exec(qs::io::process::ProcessContext(std::move(command)));
205+
}
206+
207+
void Process::exec(const qs::io::process::ProcessContext& context) {
208+
this->setRunning(false);
209+
if (context.commandSet) this->setCommand(context.command);
210+
if (context.environmentSet) this->setEnvironment(context.environment);
211+
if (context.clearEnvironmentSet) this->setEnvironmentCleared(context.clearEnvironment);
212+
if (context.workingDirectorySet) this->setWorkingDirectory(context.workingDirectory);
213+
214+
if (this->mCommand.isEmpty()) {
215+
qmlWarning(this) << "Cannot start process as command is empty.";
216+
return;
217+
}
218+
219+
this->setRunning(true);
220+
}
221+
206222
void Process::startDetached() {
207223
if (this->mCommand.isEmpty()) {
208224
qmlWarning(this) << "Cannot start process as command is empty.";
@@ -225,7 +241,7 @@ void Process::setupEnvironment(QProcess* process) {
225241
process->setWorkingDirectory(this->mWorkingDirectory);
226242
}
227243

228-
qs::core::process::setupProcessEnvironment(process, this->mClearEnvironment, this->mEnvironment);
244+
qs::io::process::setupProcessEnvironment(process, this->mClearEnvironment, this->mEnvironment);
229245
}
230246

231247
void Process::onStarted() {
@@ -245,6 +261,8 @@ void Process::onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus) {
245261
emit this->exited(exitCode, exitStatus);
246262
emit this->runningChanged();
247263
emit this->processIdChanged();
264+
265+
this->startProcessIfReady(); // for `running = false; running = true`
248266
}
249267

250268
void Process::onErrorOccurred(QProcess::ProcessError error) {

src/io/process.hpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
#include <qtypes.h>
1111
#include <qvariant.h>
1212

13+
#include "../core/doc.hpp"
1314
#include "datastream.hpp"
15+
#include "processcore.hpp"
1416

1517
// Needed when compiling with clang musl-libc++.
1618
// Default include paths contain macros that cause name collisions.
@@ -135,6 +137,40 @@ class Process: public QObject {
135137
public:
136138
explicit Process(QObject* parent = nullptr);
137139

140+
// MUST be before exec(ctx) or the other will be called with a default constructed obj.
141+
QSDOC_HIDE Q_INVOKABLE void exec(QList<QString> command);
142+
/// Launch a process with the given arguments, stopping any currently running process.
143+
///
144+
/// The context parameter can either be a list of command arguments or a JS object with the following fields:
145+
/// - `command`: A list containing the command and all its arguments. See @@Quickshell.Io.Process.command.
146+
/// - `environment`: Changes to make to the process environment. See @@Quickshell.Io.Process.environment.
147+
/// - `clearEnvironment`: Removes all variables from the environment if true.
148+
/// - `workingDirectory`: The working directory the command should run in.
149+
///
150+
/// Passed parameters will change the values currently set in the process.
151+
///
152+
/// > [!WARNING] This does not run command in a shell. All arguments to the command
153+
/// > must be in separate values in the list, e.g. `["echo", "hello"]`
154+
/// > and not `["echo hello"]`.
155+
/// >
156+
/// > Additionally, shell scripts must be run by your shell,
157+
/// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script
158+
/// > has a shebang.
159+
///
160+
/// > [!INFO] You can use `["sh", "-c", <your command>]` to execute your command with
161+
/// > the system shell.
162+
///
163+
/// Calling this function is equivalent to running:
164+
/// ```qml
165+
/// process.running = false;
166+
/// process.command = ...
167+
/// process.environment = ...
168+
/// process.clearEnvironment = ...
169+
/// process.workingDirectory = ...
170+
/// process.running = true;
171+
/// ```
172+
Q_INVOKABLE void exec(const qs::io::process::ProcessContext& context);
173+
138174
/// Sends a signal to the process if @@running is true, otherwise does nothing.
139175
Q_INVOKABLE void signal(qint32 signal);
140176

src/io/processcore.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
#include "../core/common.hpp"
99

10-
namespace qs::core::process {
10+
namespace qs::io::process {
1111

1212
void setupProcessEnvironment(
1313
QProcess* process,
@@ -34,4 +34,4 @@ void setupProcessEnvironment(
3434
process->setProcessEnvironment(env);
3535
}
3636

37-
} // namespace qs::core::process
37+
} // namespace qs::io::process

src/io/processcore.hpp

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,65 @@
11
#pragma once
22

3+
#include <utility>
4+
35
#include <qcontainerfwd.h>
46
#include <qhash.h>
7+
#include <qlist.h>
58
#include <qprocess.h>
9+
#include <qqmlintegration.h>
610
#include <qvariant.h>
711

8-
namespace qs::core::process {
12+
namespace qs::io::process {
13+
14+
class ProcessContext {
15+
Q_PROPERTY(QList<QString> command MEMBER command WRITE setCommand);
16+
Q_PROPERTY(QHash<QString, QVariant> environment MEMBER environment WRITE setEnvironment);
17+
Q_PROPERTY(bool clearEnvironment MEMBER clearEnvironment WRITE setClearEnvironment);
18+
Q_PROPERTY(QString workingDirectory MEMBER workingDirectory WRITE setWorkingDirectory);
19+
Q_GADGET;
20+
QML_STRUCTURED_VALUE;
21+
QML_VALUE_TYPE(processContext);
22+
23+
public:
24+
ProcessContext() = default;
25+
// Making this a Q_INVOKABLE does not work with QML_STRUCTURED_VALUe in Qt 6.9.
26+
explicit ProcessContext(QList<QString> command): command(std::move(command)), commandSet(true) {}
27+
28+
void setCommand(QList<QString> command) {
29+
this->command = std::move(command);
30+
this->commandSet = true;
31+
}
32+
33+
void setEnvironment(QHash<QString, QVariant> environment) {
34+
this->environment = std::move(environment);
35+
this->environmentSet = true;
36+
}
37+
38+
void setClearEnvironment(bool clearEnvironment) {
39+
this->clearEnvironment = clearEnvironment;
40+
this->clearEnvironmentSet = true;
41+
}
42+
43+
void setWorkingDirectory(QString workingDirectory) {
44+
this->workingDirectory = std::move(workingDirectory);
45+
this->workingDirectorySet = true;
46+
}
47+
48+
QList<QString> command;
49+
QHash<QString, QVariant> environment;
50+
bool clearEnvironment = false;
51+
QString workingDirectory;
52+
53+
bool commandSet : 1 = false;
54+
bool environmentSet : 1 = false;
55+
bool clearEnvironmentSet : 1 = false;
56+
bool workingDirectorySet : 1 = false;
57+
};
958

1059
void setupProcessEnvironment(
1160
QProcess* process,
1261
bool clear,
1362
const QHash<QString, QVariant>& envChanges
1463
);
1564

16-
}
65+
} // namespace qs::io::process

0 commit comments

Comments
 (0)