/* This file is part of the KDE libraries SPDX-FileCopyrightText: 2021 David Faure SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "kterminallauncherjob.h" #include #include #include #include #include #include class KTerminalLauncherJobPrivate { public: QString m_workingDirectory; QString m_command; // "ls" QString m_fullCommand; // "xterm -e ls" QString m_desktopName; QByteArray m_startupId; QProcessEnvironment m_environment{QProcessEnvironment::InheritFromParent}; }; KTerminalLauncherJob::KTerminalLauncherJob(const QString &command, QObject *parent) : KJob(parent) , d(new KTerminalLauncherJobPrivate) { d->m_command = command; } KTerminalLauncherJob::~KTerminalLauncherJob() = default; void KTerminalLauncherJob::setWorkingDirectory(const QString &workingDirectory) { d->m_workingDirectory = workingDirectory; } void KTerminalLauncherJob::setStartupId(const QByteArray &startupId) { d->m_startupId = startupId; } void KTerminalLauncherJob::setProcessEnvironment(const QProcessEnvironment &environment) { d->m_environment = environment; } void KTerminalLauncherJob::start() { determineFullCommand(); if (error()) { emitDelayedResult(); } else { auto *subjob = new KIO::CommandLauncherJob(d->m_fullCommand, this); subjob->setDesktopName(d->m_desktopName); subjob->setWorkingDirectory(d->m_workingDirectory); subjob->setStartupId(d->m_startupId); subjob->setProcessEnvironment(d->m_environment); connect(subjob, &KJob::result, this, [this, subjob] { // NB: must go through emitResult otherwise we don't get correctly finished! // TODO KF6: maybe change the base to KCompositeJob so we can get rid of this nonesense if (subjob->error()) { setError(subjob->error()); setErrorText(subjob->errorText()); } emitResult(); }); subjob->start(); } } void KTerminalLauncherJob::emitDelayedResult() { // Use delayed invocation so the caller has time to connect to the signal QMetaObject::invokeMethod(this, &KTerminalLauncherJob::emitResult, Qt::QueuedConnection); } #ifndef Q_OS_WIN // helper function to help scope service so that not the entire determineFullCommand has access to it (it is not // always not null!) static KServicePtr serviceFromConfig(bool fallbackToKonsoleService) { const KConfigGroup confGroup(KSharedConfig::openConfig(), QStringLiteral("General")); const QString terminalExec = confGroup.readEntry("TerminalApplication"); const QString terminalService = confGroup.readEntry("TerminalService"); KServicePtr service; if (!terminalService.isEmpty()) { service = KService::serviceByStorageId(terminalService); } else if (!terminalExec.isEmpty()) { service = new KService(QStringLiteral("terminal"), terminalExec, QStringLiteral("utilities-terminal")); } if (!service && fallbackToKonsoleService) { service = KService::serviceByStorageId(QStringLiteral("org.kde.konsole")); } return service; } #endif // This sets m_fullCommand, but also (when possible) m_desktopName void KTerminalLauncherJob::determineFullCommand(bool fallbackToKonsoleService /* allow synthesizing no konsole */) { const QString workingDir = d->m_workingDirectory; #ifndef Q_OS_WIN QString exec; if (KServicePtr service = serviceFromConfig(fallbackToKonsoleService); service) { d->m_desktopName = service->desktopEntryName(); exec = service->exec(); } else { // konsole not found by desktop file, let's see what PATH has for us auto useIfAvailable = [&exec](const QString &terminalApp) { const bool found = !QStandardPaths::findExecutable(terminalApp).isEmpty(); if (found) { exec = terminalApp; } return found; }; if (!useIfAvailable(QStringLiteral("konsole")) && !useIfAvailable(QStringLiteral("xterm"))) { setError(KJob::UserDefinedError); setErrorText(i18n("No terminal emulator found")); return; } } if (!d->m_command.isEmpty()) { if (exec == QLatin1String("konsole")) { exec += QLatin1String(" --noclose"); } else if (exec == QLatin1String("xterm")) { exec += QLatin1String(" -hold"); } } if (exec.startsWith(QLatin1String("konsole")) && !workingDir.isEmpty()) { exec += QLatin1String(" --workdir %1").arg(KShell::quoteArg(workingDir)); } if (!d->m_command.isEmpty()) { exec += QLatin1String(" -e ") + d->m_command; } #else const QString windowsTerminal = QStringLiteral("wt.exe"); const QString pwsh = QStringLiteral("pwsh.exe"); const QString powershell = QStringLiteral("powershell.exe"); // Powershell is used as fallback const bool hasWindowsTerminal = !QStandardPaths::findExecutable(windowsTerminal).isEmpty(); const bool hasPwsh = !QStandardPaths::findExecutable(pwsh).isEmpty(); QString exec; if (hasWindowsTerminal) { exec = windowsTerminal; if (!workingDir.isEmpty()) { exec += QLatin1String(" --startingDirectory %1").arg(KShell::quoteArg(workingDir)); } if (!d->m_command.isEmpty()) { // Command and NoExit flag will be added later exec += QLatin1Char(' ') + (hasPwsh ? pwsh : powershell); } } else { exec = hasPwsh ? pwsh : powershell; } if (!d->m_command.isEmpty()) { exec += QLatin1String(" -NoExit -Command ") + d->m_command; } #endif d->m_fullCommand = exec; } QString KTerminalLauncherJob::fullCommand() const { return d->m_fullCommand; } #include "moc_kterminallauncherjob.cpp"