File indexing completed on 2025-01-12 03:39:38
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(), QStringLiteral("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"