Skip to content

Commit 0499518

Browse files
committed
core/qmlglobal: add execDetached functions for spawning processes
1 parent 0140356 commit 0499518

File tree

10 files changed

+167
-30
lines changed

10 files changed

+167
-30
lines changed

src/core/qmlglobal.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
#include <qguiapplication.h>
99
#include <qicon.h>
1010
#include <qjsengine.h>
11+
#include <qlist.h>
1112
#include <qlogging.h>
1213
#include <qobject.h>
14+
#include <qprocess.h>
1315
#include <qqmlcontext.h>
1416
#include <qqmlengine.h>
1517
#include <qqmllist.h>
@@ -21,6 +23,7 @@
2123
#include <qwindowdefs.h>
2224
#include <unistd.h>
2325

26+
#include "../io/processcore.hpp"
2427
#include "generation.hpp"
2528
#include "iconimageprovider.hpp"
2629
#include "paths.hpp"
@@ -246,6 +249,36 @@ QVariant QuickshellGlobal::env(const QString& variable) { // NOLINT
246249
return qEnvironmentVariable(vstr.data());
247250
}
248251

252+
void QuickshellGlobal::execDetached(QList<QString> command) {
253+
QuickshellGlobal::execDetached(ProcessContext(std::move(command)));
254+
}
255+
256+
void QuickshellGlobal::execDetached(const ProcessContext& context) {
257+
if (context.command.isEmpty()) {
258+
qWarning() << "Cannot start process as command is empty.";
259+
return;
260+
}
261+
262+
const auto& cmd = context.command.first();
263+
auto args = context.command.sliced(1);
264+
265+
QProcess process;
266+
267+
qs::core::process::setupProcessEnvironment(
268+
&process,
269+
context.clearEnvironment,
270+
context.environment
271+
);
272+
273+
if (!context.workingDirectory.isEmpty()) {
274+
process.setWorkingDirectory(context.workingDirectory);
275+
}
276+
277+
process.setProgram(cmd);
278+
process.setArguments(args);
279+
process.startDetached();
280+
}
281+
249282
QString QuickshellGlobal::iconPath(const QString& icon) {
250283
return IconImageProvider::requestString(icon);
251284
}

src/core/qmlglobal.hpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
#pragma once
22

3+
#include <utility>
4+
35
#include <qclipboard.h>
46
#include <qcontainerfwd.h>
7+
#include <qhash.h>
58
#include <qjsengine.h>
9+
#include <qlist.h>
610
#include <qobject.h>
711
#include <qqmlengine.h>
812
#include <qqmlintegration.h>
@@ -15,6 +19,25 @@
1519

1620
#include "qmlscreen.hpp"
1721

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+
1841
///! Accessor for some options under the Quickshell type.
1942
class QuickshellSettings: public QObject {
2043
Q_OBJECT;
@@ -152,6 +175,46 @@ class QuickshellGlobal: public QObject {
152175
/// Returns the string value of an environment variable or null if it is not set.
153176
Q_INVOKABLE QVariant env(const QString& variable);
154177

178+
/// Launch a process detached from Quickshell.
179+
///
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:
199+
/// - `command`: A list containing the command and all its arguments. See @@Quickshell.Io.Process.command.
200+
/// - `environment`: Changes to make to the process environment. See @@Quickshell.Io.Process.environment.
201+
/// - `clearEnvironment`: Removes all variables from the environment if true.
202+
/// - `workingDirectory`: The working directory the command should run in.
203+
///
204+
/// > [!WARNING] This does not run command in a shell. All arguments to the command
205+
/// > must be in separate values in the list, e.g. `["echo", "hello"]`
206+
/// > and not `["echo hello"]`.
207+
/// >
208+
/// > Additionally, shell scripts must be run by your shell,
209+
/// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script
210+
/// > has a shebang.
211+
///
212+
/// > [!INFO] You can use `["sh", "-c", <your command>]` to execute your command with
213+
/// > the system shell.
214+
///
215+
/// This function is equivalent to @@Quickshell.Io.Process.startDetached().
216+
Q_INVOKABLE static void execDetached(const ProcessContext& context);
217+
155218
/// Returns a string usable for a @@QtQuick.Image.source for a given system icon.
156219
///
157220
/// > [!INFO] By default, icons are loaded from the theme selected by the qt platform theme,

src/core/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function (qs_test name)
22
add_executable(${name} ${ARGN})
3-
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window quickshell-ui)
3+
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-core quickshell-window quickshell-ui quickshell-io)
44
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
55
endfunction()
66

src/io/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
qt_add_library(quickshell-io STATIC
22
datastream.cpp
3+
processcore.cpp
34
process.cpp
45
fileview.cpp
56
jsonadapter.cpp

src/io/process.cpp

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
#include <utility>
44

55
#include <qdir.h>
6+
#include <qhash.h>
67
#include <qlist.h>
78
#include <qlogging.h>
8-
#include <qmap.h>
99
#include <qobject.h>
1010
#include <qprocess.h>
1111
#include <qqmlinfo.h>
1212
#include <qtmetamacros.h>
1313
#include <qtypes.h>
1414
#include <qvariant.h>
1515

16-
#include "../core/common.hpp"
1716
#include "../core/generation.hpp"
1817
#include "../core/qmlglobal.hpp"
1918
#include "datastream.hpp"
19+
#include "processcore.hpp"
2020

2121
Process::Process(QObject* parent): QObject(parent) {
2222
QObject::connect(
@@ -79,9 +79,10 @@ void Process::onGlobalWorkingDirectoryChanged() {
7979
}
8080
}
8181

82-
QMap<QString, QVariant> Process::environment() const { return this->mEnvironment; }
82+
QHash<QString, QVariant> Process::environment() const { return this->mEnvironment; }
8383

84-
void Process::setEnvironment(QMap<QString, QVariant> environment) {
84+
void Process::setEnvironment(QHash<QString, QVariant> environment) {
85+
qDebug() << "setEnv" << environment;
8586
if (environment == this->mEnvironment) return;
8687
this->mEnvironment = std::move(environment);
8788
emit this->environmentChanged();
@@ -224,24 +225,7 @@ void Process::setupEnvironment(QProcess* process) {
224225
process->setWorkingDirectory(this->mWorkingDirectory);
225226
}
226227

227-
const auto& sysenv = qs::Common::INITIAL_ENVIRONMENT;
228-
auto env = this->mClearEnvironment ? QProcessEnvironment() : sysenv;
229-
230-
for (auto& name: this->mEnvironment.keys()) {
231-
auto value = this->mEnvironment.value(name);
232-
if (!value.isValid()) continue;
233-
234-
if (this->mClearEnvironment) {
235-
if (value.isNull()) {
236-
if (sysenv.contains(name)) env.insert(name, sysenv.value(name));
237-
} else env.insert(name, value.toString());
238-
} else {
239-
if (value.isNull()) env.remove(name);
240-
else env.insert(name, value.toString());
241-
}
242-
}
243-
244-
process->setProcessEnvironment(env);
228+
qs::core::process::setupProcessEnvironment(process, this->mClearEnvironment, this->mEnvironment);
245229
}
246230

247231
void Process::onStarted() {

src/io/process.hpp

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

33
#include <qcontainerfwd.h>
4+
#include <qhash.h>
45
#include <qobject.h>
56
#include <qprocess.h>
67
#include <qqmlintegration.h>
@@ -98,7 +99,7 @@ class Process: public QObject {
9899
/// If the process is already running changing this property will affect the next
99100
/// started process. If the property has been changed after starting a process it will
100101
/// return the new value, not the one for the currently running process.
101-
Q_PROPERTY(QMap<QString, QVariant> environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
102+
Q_PROPERTY(QHash<QString, QVariant> environment READ environment WRITE setEnvironment NOTIFY environmentChanged);
102103
/// If the process's environment should be cleared prior to applying @@environment.
103104
/// Defaults to false.
104105
///
@@ -140,10 +141,12 @@ class Process: public QObject {
140141
/// Writes to the process's stdin. Does nothing if @@running is false.
141142
Q_INVOKABLE void write(const QString& data);
142143

143-
/// Launches an instance of the process detached from quickshell.
144+
/// Launches an instance of the process detached from Quickshell.
144145
///
145146
/// The subprocess will not be tracked, @@running will be false,
146147
/// and the subprocess will not be killed by Quickshell.
148+
///
149+
/// This function is equivalent to @@Quickshell.Quickshell.execDetached().
147150
Q_INVOKABLE void startDetached();
148151

149152
[[nodiscard]] bool isRunning() const;
@@ -157,8 +160,8 @@ class Process: public QObject {
157160
[[nodiscard]] QString workingDirectory() const;
158161
void setWorkingDirectory(const QString& workingDirectory);
159162

160-
[[nodiscard]] QMap<QString, QVariant> environment() const;
161-
void setEnvironment(QMap<QString, QVariant> environment);
163+
[[nodiscard]] QHash<QString, QVariant> environment() const;
164+
void setEnvironment(QHash<QString, QVariant> environment);
162165

163166
[[nodiscard]] bool environmentCleared() const;
164167
void setEnvironmentCleared(bool cleared);
@@ -203,7 +206,7 @@ private slots:
203206
QProcess* process = nullptr;
204207
QList<QString> mCommand;
205208
QString mWorkingDirectory;
206-
QMap<QString, QVariant> mEnvironment;
209+
QHash<QString, QVariant> mEnvironment;
207210
DataStreamParser* mStdoutParser = nullptr;
208211
DataStreamParser* mStderrParser = nullptr;
209212
QByteArray stdoutBuffer;

src/io/processcore.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include "processcore.hpp"
2+
3+
#include <qcontainerfwd.h>
4+
#include <qhash.h>
5+
#include <qprocess.h>
6+
#include <qvariant.h>
7+
8+
#include "../core/common.hpp"
9+
10+
namespace qs::core::process {
11+
12+
void setupProcessEnvironment(
13+
QProcess* process,
14+
bool clear,
15+
const QHash<QString, QVariant>& envChanges
16+
) {
17+
const auto& sysenv = qs::Common::INITIAL_ENVIRONMENT;
18+
auto env = clear ? QProcessEnvironment() : sysenv;
19+
20+
for (auto& name: envChanges.keys()) {
21+
auto value = envChanges.value(name);
22+
if (!value.isValid()) continue;
23+
24+
if (clear) {
25+
if (value.isNull()) {
26+
if (sysenv.contains(name)) env.insert(name, sysenv.value(name));
27+
} else env.insert(name, value.toString());
28+
} else {
29+
if (value.isNull()) env.remove(name);
30+
else env.insert(name, value.toString());
31+
}
32+
}
33+
34+
process->setProcessEnvironment(env);
35+
}
36+
37+
} // namespace qs::core::process

src/io/processcore.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#pragma once
2+
3+
#include <qcontainerfwd.h>
4+
#include <qhash.h>
5+
#include <qprocess.h>
6+
#include <qvariant.h>
7+
8+
namespace qs::core::process {
9+
10+
void setupProcessEnvironment(
11+
QProcess* process,
12+
bool clear,
13+
const QHash<QString, QVariant>& envChanges
14+
);
15+
16+
}

src/io/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function (qs_test name)
22
add_executable(${name} ${ARGN})
3-
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Network Qt::Test)
3+
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Network Qt::Test quickshell-io)
44
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
55
endfunction()
66

src/window/test/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
function (qs_test name)
22
add_executable(${name} ${ARGN})
3-
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-window quickshell-core quickshell-ui)
3+
target_link_libraries(${name} PRIVATE Qt::Quick Qt::Test quickshell-window quickshell-core quickshell-ui quickshell-io)
44
add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
55
endfunction()
66

0 commit comments

Comments
 (0)