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 \a urls.
48 *
49 * \a service the service to run
50 *
51 * \a urls the list of URLs, can be empty
52 *
53 * \a flags various flags
54 *
55 * \a suggestedFileName see KRun::setSuggestedFileName
56 *
57 * \a asn Application startup notification id, if any (otherwise "")
58 *
59 * \a serviceEntryPath the KService entryPath(), passed as an argument
60 * because in some cases it could become an empty string, e.g. if an
61 * ApplicationLauncherJob is created from a KServiceAction, the
62 * ApplicationLauncherJob will call KService::setExec() which clears the
63 * entryPath() of the KService
64 */
65 static KProcessRunner *fromApplication(const KService::Ptr &service,
66 const QString &serviceEntryPath,
67 const QList<QUrl> &urls,
68 const QString &actionName = {},
69 KIO::ApplicationLauncherJob::RunFlags flags = {},
70 const QString &suggestedFileName = {},
71 const QByteArray &asn = {});
72
73 /*!
74 * Run a shell command
75 *
76 * \a cmd must be a shell command. No need to append "&" to it.
77 *
78 * \a desktopName name of the desktop file, if known.
79 *
80 * \a execName the name of the executable, if known.
81 *
82 * \a asn Application startup notification id, if any (otherwise "").
83 *
84 * \a workingDirectory the working directory for the started process. The default
85 * (if passing an empty string) is the user's document path.
86 * This allows a command like "kwrite file.txt" to find file.txt from the right place.
87 */
88 static KProcessRunner *fromCommand(const QString &cmd,
89 const QString &desktopName,
90 const QString &execName,
91 const QByteArray &asn,
92 const QString &workingDirectory,
93 const QProcessEnvironment &environment);
94
95 /*!
96 * Run an executable with arguments (without invoking a shell, by starting a new process).
97 *
98 * \note: Starting from 5.92, if an actual executable named \a executable cannot be found
99 * in PATH, this will return a nullptr.
100 *
101 * \a executable the name of (or full path to) the executable, mandatory
102 *
103 * \a args the arguments to pass to the executable
104 *
105 * \a desktopName name of the desktop file, if known.
106 *
107 * \a asn Application startup notification id, if any (otherwise "").
108 *
109 * \a workingDirectory the working directory for the started process. The default
110 * (if passing an empty string) is the user's document path.
111 * This allows a command like "kwrite file.txt" to find file.txt from the right place.
112 */
113 static KProcessRunner *fromExecutable(const QString &executable,
114 const QStringList &args,
115 const QString &desktopName,
116 const QByteArray &asn,
117 const QString &workingDirectory,
118 const QProcessEnvironment &environment);
119
120 /*!
121 * Blocks until the process has started. Only exists for KRun via Command/ApplicationLauncherJob, will disappear in KF6.
122 */
123 virtual bool waitForStarted(int timeout = 30000) = 0;
124
125 ~KProcessRunner() override;
126
127 static int instanceCount(); // for the unittest
128
129Q_SIGNALS:
130 /*!
131 * Emitted on error. In that case, finished() is not emitted.
132 *
133 * \a errorString the error message
134 */
135 void error(const QString &errorString);
136
137 /*!
138 * Emitted when the process was successfully started
139 *
140 * \a pid PID of the process that was started
141 */
142 void processStarted(qint64 pid);
143
144protected:
145 KProcessRunner();
146 virtual void startProcess() = 0;
147 void setPid(qint64 pid);
148 void terminateStartupNotification();
149 QString name() const;
150 QString resolveServiceAlias() const;
151 static QString escapeUnitName(const QString &input);
152 void emitDelayedError(const QString &errorMsg);
153
154 std::unique_ptr<KProcess> m_process;
155 QString m_executable; // can be a full path
156 QString m_desktopName;
157 QString m_desktopFilePath;
158 QString m_description;
159 QString m_cmd;
160 qint64 m_pid = 0;
161 KService::Ptr m_service;
162 QString m_serviceEntryPath;
163 bool m_waitingForXdgToken = false;
164 QList<QUrl> m_urls;
165#if HAVE_X11
166 KStartupInfoId m_startupId;
167#endif
168
169private:
170 void initFromDesktopName(const QString &desktopName,
171 const QString &execName,
172 const QByteArray &asn,
173 const QString &workingDirectory,
174 const QProcessEnvironment &environment);
175 void init(const KService::Ptr &service, const QString &serviceEntryPath, const QString &userVisibleName, const QByteArray &asn);
176
177 Q_DISABLE_COPY(KProcessRunner)
178};
179
180class ForkingProcessRunner : public KProcessRunner
181{
182 Q_OBJECT
183
184public:
185 explicit ForkingProcessRunner();
186
187 void startProcess() override;
188 bool waitForStarted(int timeout) override;
189
190protected Q_SLOTS:
191 void slotProcessExited(int, QProcess::ExitStatus);
192 void slotProcessError(QProcess::ProcessError error);
193 virtual void slotProcessStarted();
194};
195
196#endif
197

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