1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2020 David Faure <faure@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#ifndef KPROCESSRUNNER_P_H
9#define KPROCESSRUNNER_P_H
10
11#include "applicationlauncherjob.h"
12#include "config-kiogui.h"
13#include "kiogui_export.h"
14
15#include <KProcess>
16
17#if HAVE_X11
18#include <KStartupInfo>
19#endif
20#include <QObject>
21#include <memory>
22
23namespace KIOGuiPrivate
24{
25bool checkStartupNotify(const KService *service, bool *silent_arg, QByteArray *wmclass_arg);
26}
27
28/**
29 * @internal (exported for KIO GUI job unit tests)
30 * This class runs a KService or a shell command, using QProcess internally.
31 * It creates a startup notification and finishes it on success or on error (for the taskbar)
32 * It also shows an error message if necessary (e.g. "program not found").
33 */
34class KIOGUI_EXPORT KProcessRunner : public QObject
35{
36 Q_OBJECT
37
38public:
39 enum LaunchMode {
40 Forking,
41 SystemdAsScope,
42 SystemdAsService,
43 };
44 Q_ENUM(LaunchMode)
45
46 /**
47 * Run a KService (application desktop file) to open @p urls.
48 * @param service the service to run
49 * @param urls the list of URLs, can be empty
50 * @param flags various flags
51 * @param suggestedFileName see KRun::setSuggestedFileName
52 * @param asn Application startup notification id, if any (otherwise "")
53 * @param serviceEntryPath the KService entryPath(), passed as an argument
54 * because in some cases it could become an empty string, e.g. if an
55 * ApplicationLauncherJob is created from a @c KServiceAction, the
56 * ApplicationLauncherJob will call KService::setExec() which clears the
57 * entryPath() of the KService
58 */
59 static KProcessRunner *fromApplication(const KService::Ptr &service,
60 const QString &serviceEntryPath,
61 const QList<QUrl> &urls,
62 KIO::ApplicationLauncherJob::RunFlags flags = {},
63 const QString &suggestedFileName = {},
64 const QByteArray &asn = {});
65
66 /**
67 * Run a shell command
68 * @param cmd must be a shell command. No need to append "&" to it.
69 * @param desktopName name of the desktop file, if known.
70 * @param execName the name of the executable, if known.
71 * @param asn Application startup notification id, if any (otherwise "").
72 * @param workingDirectory the working directory for the started process. The default
73 * (if passing an empty string) is the user's document path.
74 * This allows a command like "kwrite file.txt" to find file.txt from the right place.
75 */
76 static KProcessRunner *fromCommand(const QString &cmd,
77 const QString &desktopName,
78 const QString &execName,
79 const QByteArray &asn,
80 const QString &workingDirectory,
81 const QProcessEnvironment &environment);
82
83 /**
84 * Run an executable with arguments (without invoking a shell, by starting a new process).
85 *
86 * @note: Starting from 5.92, if an actual executable named @p executable cannot be found
87 * in PATH, this will return a nullptr.
88 *
89 * @param executable the name of (or full path to) the executable, mandatory
90 * @param args the arguments to pass to the executable
91 * @param desktopName name of the desktop file, if known.
92 * @param asn Application startup notification id, if any (otherwise "").
93 * @param workingDirectory the working directory for the started process. The default
94 * (if passing an empty string) is the user's document path.
95 * This allows a command like "kwrite file.txt" to find file.txt from the right place.
96 */
97 static KProcessRunner *fromExecutable(const QString &executable,
98 const QStringList &args,
99 const QString &desktopName,
100 const QByteArray &asn,
101 const QString &workingDirectory,
102 const QProcessEnvironment &environment);
103
104 /**
105 * Blocks until the process has started. Only exists for KRun via Command/ApplicationLauncherJob, will disappear in KF6.
106 */
107 virtual bool waitForStarted(int timeout = 30000) = 0;
108
109 ~KProcessRunner() override;
110
111 static int instanceCount(); // for the unittest
112
113Q_SIGNALS:
114 /**
115 * @brief Emitted on error. In that case, finished() is not emitted.
116 * @param errorString the error message
117 */
118 void error(const QString &errorString);
119
120 /**
121 * @brief emitted when the process was successfully started
122 * @param pid PID of the process that was started
123 */
124 void processStarted(qint64 pid);
125
126protected:
127 KProcessRunner();
128 virtual void startProcess() = 0;
129 void setPid(qint64 pid);
130 void terminateStartupNotification();
131 QString name() const;
132 QString resolveServiceAlias() const;
133 static QString escapeUnitName(const QString &input);
134 void emitDelayedError(const QString &errorMsg);
135
136 std::unique_ptr<KProcess> m_process;
137 QString m_executable; // can be a full path
138 QString m_desktopName;
139 QString m_desktopFilePath;
140 QString m_description;
141 QString m_cmd;
142 qint64 m_pid = 0;
143 KService::Ptr m_service;
144 QString m_serviceEntryPath;
145 bool m_waitingForXdgToken = false;
146 QList<QUrl> m_urls;
147#if HAVE_X11
148 KStartupInfoId m_startupId;
149#endif
150
151private:
152 void initFromDesktopName(const QString &desktopName,
153 const QString &execName,
154 const QByteArray &asn,
155 const QString &workingDirectory,
156 const QProcessEnvironment &environment);
157 void init(const KService::Ptr &service, const QString &serviceEntryPath, const QString &userVisibleName, const QByteArray &asn);
158
159 Q_DISABLE_COPY(KProcessRunner)
160};
161
162class ForkingProcessRunner : public KProcessRunner
163{
164 Q_OBJECT
165
166public:
167 explicit ForkingProcessRunner();
168
169 void startProcess() override;
170 bool waitForStarted(int timeout) override;
171
172protected Q_SLOTS:
173 void slotProcessExited(int, QProcess::ExitStatus);
174 void slotProcessError(QProcess::ProcessError error);
175 virtual void slotProcessStarted();
176};
177
178#endif
179

source code of kio/src/gui/kprocessrunner_p.h