File indexing completed on 2024-04-21 15:00:26

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2021 David Faure <faure@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "kterminallauncherjob.h"
0009 
0010 #include <KConfigGroup>
0011 #include <KLocalizedString>
0012 #include <KService>
0013 #include <KSharedConfig>
0014 #include <KShell>
0015 #include <QProcessEnvironment>
0016 
0017 class KTerminalLauncherJobPrivate
0018 {
0019 public:
0020     QString m_workingDirectory;
0021     QString m_command; // "ls"
0022     QString m_fullCommand; // "xterm -e ls"
0023     QString m_desktopName;
0024     QByteArray m_startupId;
0025     QProcessEnvironment m_environment;
0026 };
0027 
0028 KTerminalLauncherJob::KTerminalLauncherJob(const QString &command, QObject *parent)
0029     : KJob(parent)
0030     , d(new KTerminalLauncherJobPrivate)
0031 {
0032     d->m_command = command;
0033 }
0034 
0035 KTerminalLauncherJob::~KTerminalLauncherJob() = default;
0036 
0037 void KTerminalLauncherJob::setWorkingDirectory(const QString &workingDirectory)
0038 {
0039     d->m_workingDirectory = workingDirectory;
0040 }
0041 
0042 void KTerminalLauncherJob::setStartupId(const QByteArray &startupId)
0043 {
0044     d->m_startupId = startupId;
0045 }
0046 
0047 void KTerminalLauncherJob::setProcessEnvironment(const QProcessEnvironment &environment)
0048 {
0049     d->m_environment = environment;
0050 }
0051 
0052 void KTerminalLauncherJob::start()
0053 {
0054     determineFullCommand();
0055     if (error()) {
0056         emitDelayedResult();
0057     } else {
0058         auto *subjob = new KIO::CommandLauncherJob(d->m_fullCommand, this);
0059         subjob->setDesktopName(d->m_desktopName);
0060         subjob->setWorkingDirectory(d->m_workingDirectory);
0061         subjob->setStartupId(d->m_startupId);
0062         subjob->setProcessEnvironment(d->m_environment);
0063         connect(subjob, &KJob::result, this, [this, subjob] {
0064             // NB: must go through emitResult otherwise we don't get correctly finished!
0065             // TODO KF6: maybe change the base to KCompositeJob so we can get rid of this nonesense
0066             if (subjob->error()) {
0067                 setError(subjob->error());
0068                 setErrorText(subjob->errorText());
0069             }
0070             emitResult();
0071         });
0072         subjob->start();
0073     }
0074 }
0075 
0076 void KTerminalLauncherJob::emitDelayedResult()
0077 {
0078     // Use delayed invocation so the caller has time to connect to the signal
0079     QMetaObject::invokeMethod(this, &KTerminalLauncherJob::emitResult, Qt::QueuedConnection);
0080 }
0081 
0082 #ifndef Q_OS_WIN
0083 // helper function to help scope service so that not the entire determineFullCommand has access to it (it is not
0084 // always not null!)
0085 static KServicePtr serviceFromConfig(bool fallbackToKonsoleService)
0086 {
0087     const KConfigGroup confGroup(KSharedConfig::openConfig(), "General");
0088     const QString terminalExec = confGroup.readEntry("TerminalApplication");
0089     const QString terminalService = confGroup.readEntry("TerminalService");
0090     KServicePtr service;
0091     if (!terminalService.isEmpty()) {
0092         service = KService::serviceByStorageId(terminalService);
0093     } else if (!terminalExec.isEmpty()) {
0094         service = new KService(QStringLiteral("terminal"), terminalExec, QStringLiteral("utilities-terminal"));
0095     }
0096     if (!service && fallbackToKonsoleService) {
0097         service = KService::serviceByStorageId(QStringLiteral("org.kde.konsole"));
0098     }
0099     return service;
0100 }
0101 #endif
0102 
0103 // This sets m_fullCommand, but also (when possible) m_desktopName
0104 void KTerminalLauncherJob::determineFullCommand(bool fallbackToKonsoleService /* allow synthesizing no konsole */)
0105 {
0106     const QString workingDir = d->m_workingDirectory;
0107 #ifndef Q_OS_WIN
0108 
0109     QString exec;
0110     if (KServicePtr service = serviceFromConfig(fallbackToKonsoleService); service) {
0111         d->m_desktopName = service->desktopEntryName();
0112         exec = service->exec();
0113     } else {
0114         // konsole not found by desktop file, let's see what PATH has for us
0115         auto useIfAvailable = [&exec](const QString &terminalApp) {
0116             const bool found = !QStandardPaths::findExecutable(terminalApp).isEmpty();
0117             if (found) {
0118                 exec = terminalApp;
0119             }
0120             return found;
0121         };
0122         if (!useIfAvailable(QStringLiteral("konsole")) && !useIfAvailable(QStringLiteral("xterm"))) {
0123             setError(KJob::UserDefinedError);
0124             setErrorText(i18n("No terminal emulator found"));
0125             return;
0126         }
0127     }
0128     if (!d->m_command.isEmpty()) {
0129         if (exec == QLatin1String("konsole")) {
0130             exec += QLatin1String(" --noclose");
0131         } else if (exec == QLatin1String("xterm")) {
0132             exec += QLatin1String(" -hold");
0133         }
0134     }
0135     if (exec.startsWith(QLatin1String("konsole")) && !workingDir.isEmpty()) {
0136         exec += QLatin1String(" --workdir %1").arg(KShell::quoteArg(workingDir));
0137     }
0138     if (!d->m_command.isEmpty()) {
0139         exec += QLatin1String(" -e ") + d->m_command;
0140     }
0141 #else
0142     const QString windowsTerminal = QStringLiteral("wt.exe");
0143     const QString pwsh = QStringLiteral("pwsh.exe");
0144     const QString powershell = QStringLiteral("powershell.exe"); // Powershell is used as fallback
0145     const bool hasWindowsTerminal = !QStandardPaths::findExecutable(windowsTerminal).isEmpty();
0146     const bool hasPwsh = !QStandardPaths::findExecutable(pwsh).isEmpty();
0147 
0148     QString exec;
0149     if (hasWindowsTerminal) {
0150         exec = windowsTerminal;
0151         if (!workingDir.isEmpty()) {
0152             exec += QLatin1String(" --startingDirectory %1").arg(KShell::quoteArg(workingDir));
0153         }
0154         if (!d->m_command.isEmpty()) {
0155             // Command and NoExit flag will be added later
0156             exec += QLatin1Char(' ') + (hasPwsh ? pwsh : powershell);
0157         }
0158     } else {
0159         exec = hasPwsh ? pwsh : powershell;
0160     }
0161     if (!d->m_command.isEmpty()) {
0162         exec += QLatin1String(" -NoExit -Command ") + d->m_command;
0163     }
0164 #endif
0165     d->m_fullCommand = exec;
0166 }
0167 
0168 QString KTerminalLauncherJob::fullCommand() const
0169 {
0170     return d->m_fullCommand;
0171 }
0172 
0173 #include "moc_kterminallauncherjob.cpp"