KIO

kterminallauncherjob.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2021 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#include "kterminallauncherjob.h"
9
10#include <KConfigGroup>
11#include <KLocalizedString>
12#include <KService>
13#include <KSharedConfig>
14#include <KShell>
15#include <QProcessEnvironment>
16
17class KTerminalLauncherJobPrivate
18{
19public:
20 QString m_workingDirectory;
21 QString m_command; // "ls"
22 QString m_fullCommand; // "xterm -e ls"
23 QString m_desktopName;
24 QByteArray m_startupId;
26};
27
29 : KJob(parent)
30 , d(new KTerminalLauncherJobPrivate)
31{
32 d->m_command = command;
33}
34
36
38{
39 d->m_workingDirectory = workingDirectory;
40}
41
43{
44 d->m_startupId = startupId;
45}
46
48{
49 d->m_environment = environment;
50}
51
53{
54 determineFullCommand();
55 if (error()) {
56 emitDelayedResult();
57 } else {
58 auto *subjob = new KIO::CommandLauncherJob(d->m_fullCommand, this);
59 subjob->setDesktopName(d->m_desktopName);
60 subjob->setWorkingDirectory(d->m_workingDirectory);
61 subjob->setStartupId(d->m_startupId);
62 subjob->setProcessEnvironment(d->m_environment);
63 connect(subjob, &KJob::result, this, [this, subjob] {
64 // NB: must go through emitResult otherwise we don't get correctly finished!
65 // TODO KF6: maybe change the base to KCompositeJob so we can get rid of this nonesense
66 if (subjob->error()) {
67 setError(subjob->error());
68 setErrorText(subjob->errorText());
69 }
70 emitResult();
71 });
72 subjob->start();
73 }
74}
75
76void KTerminalLauncherJob::emitDelayedResult()
77{
78 // Use delayed invocation so the caller has time to connect to the signal
80}
81
82#ifndef Q_OS_WIN
83// helper function to help scope service so that not the entire determineFullCommand has access to it (it is not
84// always not null!)
85static KServicePtr serviceFromConfig(bool fallbackToKonsoleService)
86{
87 const KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General"));
88 const QString terminalExec = confGroup.readEntry("TerminalApplication");
89 const QString terminalService = confGroup.readEntry("TerminalService");
90 KServicePtr service;
91 if (!terminalService.isEmpty()) {
92 service = KService::serviceByStorageId(terminalService);
93 } else if (!terminalExec.isEmpty()) {
94 service = new KService(QStringLiteral("terminal"), terminalExec, QStringLiteral("utilities-terminal"));
95 }
96 if (!service && fallbackToKonsoleService) {
97 service = KService::serviceByStorageId(QStringLiteral("org.kde.konsole"));
98 }
99 return service;
100}
101#endif
102
103// This sets m_fullCommand, but also (when possible) m_desktopName
104void KTerminalLauncherJob::determineFullCommand(bool fallbackToKonsoleService /* allow synthesizing no konsole */)
105{
106 const QString workingDir = d->m_workingDirectory;
107#ifndef Q_OS_WIN
108
110 if (KServicePtr service = serviceFromConfig(fallbackToKonsoleService); service) {
111 d->m_desktopName = service->desktopEntryName();
112 exec = service->exec();
113 } else {
114 // konsole not found by desktop file, let's see what PATH has for us
115 auto useIfAvailable = [&exec](const QString &terminalApp) {
116 const bool found = !QStandardPaths::findExecutable(terminalApp).isEmpty();
117 if (found) {
118 exec = terminalApp;
119 }
120 return found;
121 };
122 if (!useIfAvailable(QStringLiteral("konsole")) && !useIfAvailable(QStringLiteral("xterm"))) {
123 setError(KJob::UserDefinedError);
124 setErrorText(i18n("No terminal emulator found"));
125 return;
126 }
127 }
128 if (!d->m_command.isEmpty()) {
129 if (exec == QLatin1String("konsole")) {
130 exec += QLatin1String(" --noclose");
131 } else if (exec == QLatin1String("xterm")) {
132 exec += QLatin1String(" -hold");
133 }
134 }
135 if (exec.startsWith(QLatin1String("konsole")) && !workingDir.isEmpty()) {
136 exec += QLatin1String(" --workdir %1").arg(KShell::quoteArg(workingDir));
137 }
138 if (!d->m_command.isEmpty()) {
139 exec += QLatin1String(" -e ") + d->m_command;
140 }
141#else
142 const QString windowsTerminal = QStringLiteral("wt.exe");
143 const QString pwsh = QStringLiteral("pwsh.exe");
144 const QString powershell = QStringLiteral("powershell.exe"); // Powershell is used as fallback
145 const bool hasWindowsTerminal = !QStandardPaths::findExecutable(windowsTerminal).isEmpty();
146 const bool hasPwsh = !QStandardPaths::findExecutable(pwsh).isEmpty();
147
149 if (hasWindowsTerminal) {
150 exec = windowsTerminal;
151 if (!workingDir.isEmpty()) {
152 exec += QLatin1String(" --startingDirectory %1").arg(KShell::quoteArg(workingDir));
153 }
154 if (!d->m_command.isEmpty()) {
155 // Command and NoExit flag will be added later
156 exec += QLatin1Char(' ') + (hasPwsh ? pwsh : powershell);
157 }
158 } else {
159 exec = hasPwsh ? pwsh : powershell;
160 }
161 if (!d->m_command.isEmpty()) {
162 exec += QLatin1String(" -NoExit -Command ") + d->m_command;
163 }
164#endif
165 d->m_fullCommand = exec;
166}
167
168QString KTerminalLauncherJob::fullCommand() const
169{
170 return d->m_fullCommand;
171}
172
173#include "moc_kterminallauncherjob.cpp"
CommandLauncherJob runs a command and watches it while running.
bool exec()
void setErrorText(const QString &errorText)
void emitResult()
int error() const
void result(KJob *job)
void setError(int errorCode)
static Ptr serviceByStorageId(const QString &_storageId)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
void setStartupId(const QByteArray &startupId)
Sets the platform-specific startup id of the command launch.
~KTerminalLauncherJob() override
Destructor.
void setProcessEnvironment(const QProcessEnvironment &environment)
Can be used to pass environment variables to the child process.
KTerminalLauncherJob(const QString &command, QObject *parent=nullptr)
Creates a KTerminalLauncherJob.
void setWorkingDirectory(const QString &workingDirectory)
Sets the working directory from which to run the command.
void start() override
Starts the job.
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QString quoteArg(const QString &arg)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString findExecutable(const QString &executableName, const QStringList &paths)
bool isEmpty() const const
QueuedConnection
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.