• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.9.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

krun.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002     Copyright (C) 2000 Torben Weis <weis@kde.org>
00003     Copyright (C) 2006 David Faure <faure@kde.org>
00004     Copyright (C) 2009 Michael Pyne <michael.pyne@kdemail.net>
00005 
00006     This library is free software; you can redistribute it and/or
00007     modify it under the terms of the GNU Library General Public
00008     License as published by the Free Software Foundation; either
00009     version 2 of the License, or (at your option) any later version.
00010 
00011     This library is distributed in the hope that it will be useful,
00012     but WITHOUT ANY WARRANTY; without even the implied warranty of
00013     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014     Library General Public License for more details.
00015 
00016     You should have received a copy of the GNU Library General Public License
00017     along with this library; see the file COPYING.LIB.  If not, write to
00018     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00019     Boston, MA 02110-1301, USA.
00020 */
00021 
00022 #include "krun.h"
00023 #include "krun_p.h"
00024 
00025 #include <config.h>
00026 
00027 #include <assert.h>
00028 #include <stdlib.h>
00029 #include <string.h>
00030 #include <unistd.h>
00031 #include <typeinfo>
00032 #include <sys/stat.h>
00033 
00034 #include <QtGui/QWidget>
00035 #include <QtGui/QLabel>
00036 #include <QtGui/QVBoxLayout>
00037 #include <QtGui/QHBoxLayout>
00038 #include <QtGui/QPlainTextEdit>
00039 #include <QtGui/QApplication>
00040 #include <QtGui/QDesktopWidget>
00041 
00042 #include <kmimetypetrader.h>
00043 #include <kmimetype.h>
00044 #include "kio/jobclasses.h" // for KIO::JobFlags
00045 #include "kio/job.h"
00046 #include "kio/jobuidelegate.h"
00047 #include "kio/global.h"
00048 #include "kio/scheduler.h"
00049 #include "kio/netaccess.h"
00050 #include "kfile/kopenwithdialog.h"
00051 #include "kfile/krecentdocument.h"
00052 #include "kdesktopfileactions.h"
00053 
00054 #include <kauthorized.h>
00055 #include <kmessageboxwrapper.h>
00056 #include <kurl.h>
00057 #include <kglobal.h>
00058 #include <ktoolinvocation.h>
00059 #include <kdebug.h>
00060 #include <klocale.h>
00061 #include <kprotocolmanager.h>
00062 #include <kstandarddirs.h>
00063 #include <kprocess.h>
00064 #include <QtCore/QFile>
00065 #include <QtCore/QFileInfo>
00066 #include <QtCore/QTextIStream>
00067 #include <QtCore/QDate>
00068 #include <QtCore/QRegExp>
00069 #include <QDir>
00070 #include <kdesktopfile.h>
00071 #include <kmacroexpander.h>
00072 #include <kshell.h>
00073 #include <QTextDocument>
00074 #include <kde_file.h>
00075 #include <kconfiggroup.h>
00076 #include <kdialog.h>
00077 #include <kstandardguiitem.h>
00078 #include <kguiitem.h>
00079 #include <ksavefile.h>
00080 
00081 #ifdef Q_WS_X11
00082 #include <kwindowsystem.h>
00083 #endif
00084 
00085 KRun::KRunPrivate::KRunPrivate(KRun *parent)
00086         : q(parent),
00087         m_showingDialog(false)
00088 {
00089 }
00090 
00091 void KRun::KRunPrivate::startTimer()
00092 {
00093     m_timer.start(0);
00094 }
00095 
00096 // ---------------------------------------------------------------------------
00097 
00098 bool KRun::isExecutableFile(const KUrl& url, const QString &mimetype)
00099 {
00100     if (!url.isLocalFile()) {
00101         return false;
00102     }
00103     QFileInfo file(url.toLocalFile());
00104     if (file.isExecutable()) {    // Got a prospective file to run
00105         KMimeType::Ptr mimeType = KMimeType::mimeType(mimetype, KMimeType::ResolveAliases);
00106         if (mimeType && (mimeType->is(QLatin1String("application/x-executable")) ||
00107 #ifdef Q_WS_WIN
00108                          mimeType->is(QLatin1String("application/x-ms-dos-executable")) ||
00109 #endif
00110                          mimeType->is(QLatin1String("application/x-executable-script")))
00111            )
00112         {
00113             return true;
00114         }
00115     }
00116     return false;
00117 }
00118 
00119 // This is called by foundMimeType, since it knows the mimetype of the URL
00120 bool KRun::runUrl(const KUrl& u, const QString& _mimetype, QWidget* window, bool tempFile, bool runExecutables, const QString& suggestedFileName, const QByteArray& asn)
00121 {
00122     bool noRun = false;
00123     bool noAuth = false;
00124     if (_mimetype == QLatin1String("inode/directory-locked")) {
00125         KMessageBoxWrapper::error(window,
00126                                   i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", Qt::escape(u.prettyUrl())));
00127         return false;
00128     }
00129     else if (_mimetype == QLatin1String("application/x-desktop")) {
00130         if (u.isLocalFile() && runExecutables) {
00131             return KDesktopFileActions::run(u, true);
00132         }
00133     }
00134     else if (isExecutableFile(u, _mimetype)) {
00135         if (u.isLocalFile() && runExecutables) {
00136             if (KAuthorized::authorize("shell_access")) {
00137                 return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn, u.directory())); // just execute the url as a command
00138                 // ## TODO implement deleting the file if tempFile==true
00139             }
00140             else {
00141                 noAuth = true;
00142             }
00143         }
00144         else if (_mimetype == QLatin1String("application/x-executable")) {
00145             noRun = true;
00146         }
00147     }
00148     else if (isExecutable(_mimetype)) {
00149         if (!runExecutables) {
00150             noRun = true;
00151         }
00152 
00153         if (!KAuthorized::authorize("shell_access")) {
00154             noAuth = true;
00155         }
00156     }
00157 
00158     if (noRun) {
00159         KMessageBox::sorry(window,
00160                            i18n("<qt>The file <b>%1</b> is an executable program. "
00161                                 "For safety it will not be started.</qt>", Qt::escape(u.prettyUrl())));
00162         return false;
00163     }
00164     if (noAuth) {
00165         KMessageBoxWrapper::error(window,
00166                                   i18n("<qt>You do not have permission to run <b>%1</b>.</qt>", Qt::escape(u.prettyUrl())));
00167         return false;
00168     }
00169 
00170     KUrl::List lst;
00171     lst.append(u);
00172 
00173     KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype);
00174 
00175     if (!offer) {
00176         // Open-with dialog
00177         // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
00178         // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list...
00179         return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn);
00180     }
00181 
00182     return KRun::run(*offer, lst, window, tempFile, suggestedFileName, asn);
00183 }
00184 
00185 bool KRun::displayOpenWithDialog(const KUrl::List& lst, QWidget* window, bool tempFiles,
00186                                  const QString& suggestedFileName, const QByteArray& asn)
00187 {
00188     if (!KAuthorized::authorizeKAction("openwith")) {
00189         KMessageBox::sorry(window,
00190                            i18n("You are not authorized to select an application to open this file."));
00191         return false;
00192     }
00193 
00194 #ifdef Q_WS_WIN
00195     KConfigGroup cfgGroup(KGlobal::config(), "KOpenWithDialog Settings");
00196     if (cfgGroup.readEntry("Native", true)) {
00197         return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles,
00198                 suggestedFileName, asn);
00199     }
00200 #endif
00201     KOpenWithDialog l(lst, i18n("Open with:"), QString(), window);
00202     if (l.exec()) {
00203         KService::Ptr service = l.service();
00204         if (!service) {
00205             kDebug(7010) << "No service set, running " << l.text();
00206             service = KService::Ptr(new KService(QString() /*name*/, l.text(), QString() /*icon*/));
00207         }
00208         return KRun::run(*service, lst, window, tempFiles, suggestedFileName, asn);
00209     }
00210     return false;
00211 }
00212 
00213 #ifndef KDE_NO_DEPRECATED
00214 void KRun::shellQuote(QString &_str)
00215 {
00216     // Credits to Walter, says Bernd G. :)
00217     if (_str.isEmpty()) { // Don't create an explicit empty parameter
00218         return;
00219     }
00220     QChar q('\'');
00221     _str.replace(q, "'\\''").prepend(q).append(q);
00222 }
00223 #endif
00224 
00225 
00226 class KRunMX1 : public KMacroExpanderBase
00227 {
00228 public:
00229     KRunMX1(const KService &_service) :
00230             KMacroExpanderBase('%'), hasUrls(false), hasSpec(false), service(_service) {}
00231 
00232     bool hasUrls: 1, hasSpec: 1;
00233 
00234 protected:
00235     virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
00236 
00237 private:
00238     const KService &service;
00239 };
00240 
00241 int
00242 KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
00243 {
00244     uint option = str[pos + 1].unicode();
00245     switch (option) {
00246     case 'c':
00247         ret << service.name().replace('%', "%%");
00248         break;
00249     case 'k':
00250         ret << service.entryPath().replace('%', "%%");
00251         break;
00252     case 'i':
00253         ret << "--icon" << service.icon().replace('%', "%%");
00254         break;
00255     case 'm':
00256 //       ret << "-miniicon" << service.icon().replace( '%', "%%" );
00257         kWarning() << "-miniicon isn't supported anymore (service"
00258         << service.name() << ')';
00259         break;
00260     case 'u':
00261     case 'U':
00262         hasUrls = true;
00263         /* fallthrough */
00264     case 'f':
00265     case 'F':
00266     case 'n':
00267     case 'N':
00268     case 'd':
00269     case 'D':
00270     case 'v':
00271         hasSpec = true;
00272         /* fallthrough */
00273     default:
00274         return -2; // subst with same and skip
00275     }
00276     return 2;
00277 }
00278 
00279 class KRunMX2 : public KMacroExpanderBase
00280 {
00281 public:
00282     KRunMX2(const KUrl::List &_urls) :
00283             KMacroExpanderBase('%'), ignFile(false), urls(_urls) {}
00284 
00285     bool ignFile: 1;
00286 
00287 protected:
00288     virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
00289 
00290 private:
00291     void subst(int option, const KUrl &url, QStringList &ret);
00292 
00293     const KUrl::List &urls;
00294 };
00295 
00296 void
00297 KRunMX2::subst(int option, const KUrl &url, QStringList &ret)
00298 {
00299     switch (option) {
00300     case 'u':
00301         ret << ((url.isLocalFile() && url.fragment().isNull() && url.encodedQuery().isNull()) ?
00302                 QDir::toNativeSeparators(url.toLocalFile())  : url.url());
00303         break;
00304     case 'd':
00305         ret << url.directory();
00306         break;
00307     case 'f':
00308         ret << QDir::toNativeSeparators(url.toLocalFile());
00309         break;
00310     case 'n':
00311         ret << url.fileName();
00312         break;
00313     case 'v':
00314         if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
00315             ret << KDesktopFile(url.toLocalFile()).desktopGroup().readEntry("Dev");
00316         }
00317         break;
00318     }
00319     return;
00320 }
00321 
00322 int
00323 KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
00324 {
00325     uint option = str[pos + 1].unicode();
00326     switch (option) {
00327     case 'f':
00328     case 'u':
00329     case 'n':
00330     case 'd':
00331     case 'v':
00332         if (urls.isEmpty()) {
00333             if (!ignFile) {
00334                 kDebug() << "No URLs supplied to single-URL service" << str;
00335             }
00336         }
00337         else if (urls.count() > 1) {
00338             kWarning() << urls.count() << "URLs supplied to single-URL service" << str;
00339         }
00340         else {
00341             subst(option, urls.first(), ret);
00342         }
00343         break;
00344     case 'F':
00345     case 'U':
00346     case 'N':
00347     case 'D':
00348         option += 'a' - 'A';
00349         for (KUrl::List::ConstIterator it = urls.begin(); it != urls.end(); ++it)
00350             subst(option, *it, ret);
00351         break;
00352     case '%':
00353         ret = QStringList(QLatin1String("%"));
00354         break;
00355     default:
00356         return -2; // subst with same and skip
00357     }
00358     return 2;
00359 }
00360 
00361 static QStringList supportedProtocols(const KService& _service)
00362 {
00363     // Check which protocols the application supports.
00364     // This can be a list of actual protocol names, or just KIO for KDE apps.
00365     QStringList supportedProtocols = _service.property("X-KDE-Protocols").toStringList();
00366     KRunMX1 mx1(_service);
00367     QString exec = _service.exec();
00368     if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
00369         Q_ASSERT(supportedProtocols.isEmpty());   // huh? If you support protocols you need %u or %U...
00370     }
00371     else {
00372         if (supportedProtocols.isEmpty()) {
00373             // compat mode: assume KIO if not set and it's a KDE app (or a KDE service)
00374             const QStringList categories = _service.property("Categories").toStringList();
00375             if (categories.contains("KDE")
00376                     || !_service.isApplication()
00377                     || _service.entryPath().isEmpty() /*temp service*/) {
00378                 supportedProtocols.append("KIO");
00379             }
00380             else { // if no KDE app, be a bit over-generic
00381                 supportedProtocols.append("http");
00382                 supportedProtocols.append("https"); // #253294
00383                 supportedProtocols.append("ftp");
00384             }
00385         }
00386     }
00387     kDebug(7010) << "supportedProtocols:" << supportedProtocols;
00388     return supportedProtocols;
00389 }
00390 
00391 static bool isProtocolInSupportedList(const KUrl& url, const QStringList& supportedProtocols)
00392 {
00393     if (supportedProtocols.contains("KIO"))
00394         return true;
00395     return url.isLocalFile() || supportedProtocols.contains(url.protocol().toLower());
00396 }
00397 
00398 QStringList KRun::processDesktopExec(const KService &_service, const KUrl::List& _urls, bool tempFiles, const QString& suggestedFileName)
00399 {
00400     QString exec = _service.exec();
00401     if (exec.isEmpty()) {
00402         kWarning() << "KRun: no Exec field in `" << _service.entryPath() << "' !";
00403         return QStringList();
00404     }
00405 
00406     QStringList result;
00407     bool appHasTempFileOption;
00408 
00409     KRunMX1 mx1(_service);
00410     KRunMX2 mx2(_urls);
00411 
00412     if (!mx1.expandMacrosShellQuote(exec)) {    // Error in shell syntax
00413         kWarning() << "KRun: syntax error in command" << _service.exec() << ", service" << _service.name();
00414         return QStringList();
00415     }
00416 
00417     // FIXME: the current way of invoking kioexec disables term and su use
00418 
00419     // Check if we need "tempexec" (kioexec in fact)
00420     appHasTempFileOption = tempFiles && _service.property("X-KDE-HasTempFileOption").toBool();
00421     if (tempFiles && !appHasTempFileOption && _urls.size()) {
00422         const QString kioexec = KStandardDirs::findExe("kioexec");
00423         Q_ASSERT(!kioexec.isEmpty());
00424         result << kioexec << "--tempfiles" << exec;
00425         if (!suggestedFileName.isEmpty()) {
00426             result << "--suggestedfilename";
00427             result << suggestedFileName;
00428         }
00429         result += _urls.toStringList();
00430         return result;
00431     }
00432 
00433     // Check if we need kioexec
00434     bool useKioexec = false;
00435     if (!mx1.hasUrls) {
00436         for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
00437             if (!(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it)) {
00438                 useKioexec = true;
00439                 kDebug(7010) << "non-local files, application does not support urls, using kioexec";
00440                 break;
00441             }
00442     } else { // app claims to support %u/%U, check which protocols
00443         QStringList appSupportedProtocols = supportedProtocols(_service);
00444         for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
00445             if (!isProtocolInSupportedList(*it, appSupportedProtocols) && !KProtocolInfo::isHelperProtocol(*it)) {
00446                 useKioexec = true;
00447                 kDebug(7010) << "application does not support url, using kioexec:" << *it;
00448                 break;
00449             }
00450     }
00451     if (useKioexec) {
00452         // We need to run the app through kioexec
00453         const QString kioexec = KStandardDirs::findExe("kioexec");
00454         Q_ASSERT(!kioexec.isEmpty());
00455         result << kioexec;
00456         if (tempFiles) {
00457             result << "--tempfiles";
00458         }
00459         if (!suggestedFileName.isEmpty()) {
00460             result << "--suggestedfilename";
00461             result << suggestedFileName;
00462         }
00463         result << exec;
00464         result += _urls.toStringList();
00465         return result;
00466     }
00467 
00468     if (appHasTempFileOption) {
00469         exec += " --tempfile";
00470     }
00471 
00472     // Did the user forget to append something like '%f'?
00473     // If so, then assume that '%f' is the right choice => the application
00474     // accepts only local files.
00475     if (!mx1.hasSpec) {
00476         exec += " %f";
00477         mx2.ignFile = true;
00478     }
00479 
00480     mx2.expandMacrosShellQuote(exec);   // syntax was already checked, so don't check return value
00481 
00482     /*
00483      1 = need_shell, 2 = terminal, 4 = su
00484 
00485      0                                                           << split(cmd)
00486      1                                                           << "sh" << "-c" << cmd
00487      2 << split(term) << "-e"                                    << split(cmd)
00488      3 << split(term) << "-e"                                    << "sh" << "-c" << cmd
00489 
00490      4                        << "kdesu" << "-u" << user << "-c" << cmd
00491      5                        << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
00492      6 << split(term) << "-e" << "su"            << user << "-c" << cmd
00493      7 << split(term) << "-e" << "su"            << user << "-c" << ("sh -c " + quote(cmd))
00494 
00495      "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
00496      this could be optimized with the -s switch of some su versions (e.g., debian linux).
00497     */
00498 
00499     if (_service.terminal()) {
00500         KConfigGroup cg(KGlobal::config(), "General");
00501         QString terminal = cg.readPathEntry("TerminalApplication", "konsole");
00502         if (terminal == "konsole") {
00503             if (!_service.path().isEmpty()) {
00504                 terminal += " --workdir " + KShell::quoteArg(_service.path());
00505             }
00506             terminal += " -caption=%c %i %m";
00507         }
00508         terminal += ' ';
00509         terminal += _service.terminalOptions();
00510         if (!mx1.expandMacrosShellQuote(terminal)) {
00511             kWarning() << "KRun: syntax error in command" << terminal << ", service" << _service.name();
00512             return QStringList();
00513         }
00514         mx2.expandMacrosShellQuote(terminal);
00515         result = KShell::splitArgs(terminal);   // assuming that the term spec never needs a shell!
00516         result << "-e";
00517     }
00518 
00519     KShell::Errors err;
00520     QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
00521     if (err == KShell::NoError && !execlist.isEmpty()) { // mx1 checked for syntax errors already
00522         // Resolve the executable to ensure that helpers in lib/kde4/libexec/ are found.
00523         // Too bad for commands that need a shell - they must reside in $PATH.
00524         const QString exePath = KStandardDirs::findExe(execlist[0]);
00525         if (!exePath.isEmpty()) {
00526             execlist[0] = exePath;
00527         }
00528     }
00529     if (_service.substituteUid()) {
00530         if (_service.terminal()) {
00531             result << "su";
00532         }
00533         else {
00534             result << KStandardDirs::findExe("kdesu") << "-u";
00535         }
00536 
00537         result << _service.username() << "-c";
00538         if (err == KShell::FoundMeta) {
00539             exec = "/bin/sh -c " + KShell::quoteArg(exec);
00540         }
00541         else {
00542             exec = KShell::joinArgs(execlist);
00543         }
00544         result << exec;
00545     }
00546     else {
00547         if (err == KShell::FoundMeta) {
00548             result << "/bin/sh" << "-c" << exec;
00549         }
00550         else {
00551             result += execlist;
00552         }
00553     }
00554 
00555     return result;
00556 }
00557 
00558 //static
00559 QString KRun::binaryName(const QString & execLine, bool removePath)
00560 {
00561     // Remove parameters and/or trailing spaces.
00562     const QStringList args = KShell::splitArgs(execLine);
00563     for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
00564         if (!(*it).contains('=')) {
00565             // Remove path if wanted
00566             return removePath ? (*it).mid((*it).lastIndexOf('/') + 1) : *it;
00567         }
00568     return QString();
00569 }
00570 
00571 static bool runCommandInternal(KProcess* proc, const KService* service, const QString& executable,
00572                                const QString &userVisibleName, const QString & iconName, QWidget* window,
00573                                const QByteArray& asn)
00574 {
00575     if (window != NULL) {
00576         window = window->topLevelWidget();
00577     }
00578     if (service && !service->entryPath().isEmpty()
00579             && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath()))
00580     {
00581         kWarning() << "No authorization to execute " << service->entryPath();
00582         KMessageBox::sorry(window, i18n("You are not authorized to execute this file."));
00583         delete proc;
00584         return false;
00585     }
00586 
00587     QString bin = KRun::binaryName(executable, true);
00588 #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
00589     bool silent;
00590     QByteArray wmclass;
00591     KStartupInfoId id;
00592     bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass));
00593     if (startup_notify) {
00594         id.initId(asn);
00595         id.setupStartupEnv();
00596         KStartupInfoData data;
00597         data.setHostname();
00598         data.setBin(bin);
00599         if (!userVisibleName.isEmpty()) {
00600             data.setName(userVisibleName);
00601         }
00602         else if (service && !service->name().isEmpty()) {
00603             data.setName(service->name());
00604         }
00605         data.setDescription(i18n("Launching %1" ,  data.name()));
00606         if (!iconName.isEmpty()) {
00607             data.setIcon(iconName);
00608         }
00609         else if (service && !service->icon().isEmpty()) {
00610             data.setIcon(service->icon());
00611         }
00612         if (!wmclass.isEmpty()) {
00613             data.setWMClass(wmclass);
00614         }
00615         if (silent) {
00616             data.setSilent(KStartupInfoData::Yes);
00617         }
00618         data.setDesktop(KWindowSystem::currentDesktop());
00619         if (window) {
00620             data.setLaunchedBy(window->winId());
00621         }
00622         if(service && !service->entryPath().isEmpty())
00623             data.setApplicationId(service->entryPath());
00624         KStartupInfo::sendStartup(id, data);
00625     }
00626     int pid = KProcessRunner::run(proc, executable, id);
00627     if (startup_notify && pid) {
00628         KStartupInfoData data;
00629         data.addPid(pid);
00630         KStartupInfo::sendChange(id, data);
00631         KStartupInfo::resetStartupEnv();
00632     }
00633     return pid != 0;
00634 #else
00635     Q_UNUSED(userVisibleName);
00636     Q_UNUSED(iconName);
00637     return KProcessRunner::run(proc, bin) != 0;
00638 #endif
00639 }
00640 
00641 // This code is also used in klauncher.
00642 bool KRun::checkStartupNotify(const QString& /*binName*/, const KService* service, bool* silent_arg, QByteArray* wmclass_arg)
00643 {
00644     bool silent = false;
00645     QByteArray wmclass;
00646     if (service && service->property("StartupNotify").isValid()) {
00647         silent = !service->property("StartupNotify").toBool();
00648         wmclass = service->property("StartupWMClass").toString().toLatin1();
00649     }
00650     else if (service && service->property("X-KDE-StartupNotify").isValid()) {
00651         silent = !service->property("X-KDE-StartupNotify").toBool();
00652         wmclass = service->property("X-KDE-WMClass").toString().toLatin1();
00653     }
00654     else { // non-compliant app
00655         if (service) {
00656             if (service->isApplication()) { // doesn't have .desktop entries needed, start as non-compliant
00657                 wmclass = "0"; // krazy:exclude=doublequote_chars
00658             }
00659             else {
00660                 return false; // no startup notification at all
00661             }
00662         }
00663         else {
00664 #if 0
00665             // Create startup notification even for apps for which there shouldn't be any,
00666             // just without any visual feedback. This will ensure they'll be positioned on the proper
00667             // virtual desktop, and will get user timestamp from the ASN ID.
00668             wmclass = '0';
00669             silent = true;
00670 #else   // That unfortunately doesn't work, when the launched non-compliant application
00671             // launches another one that is compliant and there is any delay inbetween (bnc:#343359)
00672             return false;
00673 #endif
00674         }
00675     }
00676     if (silent_arg != NULL) {
00677         *silent_arg = silent;
00678     }
00679     if (wmclass_arg != NULL) {
00680         *wmclass_arg = wmclass;
00681     }
00682     return true;
00683 }
00684 
00685 static bool runTempService(const KService& _service, const KUrl::List& _urls, QWidget* window,
00686                            bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
00687 {
00688     if (!_urls.isEmpty()) {
00689         kDebug(7010) << "runTempService: first url " << _urls.first().url();
00690     }
00691 
00692     QStringList args;
00693     if ((_urls.count() > 1) && !_service.allowMultipleFiles()) {
00694         // We need to launch the application N times. That sucks.
00695         // We ignore the result for application 2 to N.
00696         // For the first file we launch the application in the
00697         // usual way. The reported result is based on this
00698         // application.
00699         KUrl::List::ConstIterator it = _urls.begin();
00700         while (++it != _urls.end()) {
00701             KUrl::List singleUrl;
00702             singleUrl.append(*it);
00703             runTempService(_service, singleUrl, window, tempFiles, suggestedFileName, QByteArray());
00704         }
00705         KUrl::List singleUrl;
00706         singleUrl.append(_urls.first());
00707         args = KRun::processDesktopExec(_service, singleUrl, tempFiles, suggestedFileName);
00708     }
00709     else {
00710         args = KRun::processDesktopExec(_service, _urls, tempFiles, suggestedFileName);
00711     }
00712     if (args.isEmpty()) {
00713         KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath()));
00714         return false;
00715     }
00716     kDebug(7010) << "runTempService: KProcess args=" << args;
00717 
00718     KProcess * proc = new KProcess;
00719     *proc << args;
00720 
00721     if (!_service.path().isEmpty()) {
00722         proc->setWorkingDirectory(_service.path());
00723     }
00724 
00725     return runCommandInternal(proc, &_service, KRun::binaryName(_service.exec(), false),
00726                               _service.name(), _service.icon(), window, asn);
00727 }
00728 
00729 // WARNING: don't call this from processDesktopExec, since klauncher uses that too...
00730 static KUrl::List resolveURLs(const KUrl::List& _urls, const KService& _service)
00731 {
00732     // Check which protocols the application supports.
00733     // This can be a list of actual protocol names, or just KIO for KDE apps.
00734     QStringList appSupportedProtocols = supportedProtocols(_service);
00735     KUrl::List urls(_urls);
00736     if (!appSupportedProtocols.contains("KIO")) {
00737         for (KUrl::List::Iterator it = urls.begin(); it != urls.end(); ++it) {
00738             const KUrl url = *it;
00739             bool supported = isProtocolInSupportedList(url, appSupportedProtocols);
00740             kDebug(7010) << "Looking at url=" << url << " supported=" << supported;
00741             if (!supported && KProtocolInfo::protocolClass(url.protocol()) == ":local") {
00742                 // Maybe we can resolve to a local URL?
00743                 KUrl localURL = KIO::NetAccess::mostLocalUrl(url, 0);
00744                 if (localURL != url) {
00745                     *it = localURL;
00746                     kDebug(7010) << "Changed to " << localURL;
00747                 }
00748             }
00749         }
00750     }
00751     return urls;
00752 }
00753 
00754 // Simple KDialog that resizes the given text edit after being shown to more
00755 // or less fit the enclosed text.
00756 class SecureMessageDialog : public KDialog
00757 {
00758     public:
00759     SecureMessageDialog(QWidget *parent) : KDialog(parent), m_textEdit(0)
00760     {
00761     }
00762 
00763     void setTextEdit(QPlainTextEdit *textEdit)
00764     {
00765         m_textEdit = textEdit;
00766     }
00767 
00768     protected:
00769     virtual void showEvent(QShowEvent* e)
00770     {
00771         // Now that we're shown, use our width to calculate a good
00772         // bounding box for the text, and resize m_textEdit appropriately.
00773         KDialog::showEvent(e);
00774 
00775         if(!m_textEdit)
00776             return;
00777 
00778         QSize fudge(20, 24); // About what it sounds like :-/
00779 
00780         // Form rect with a lot of height for bounding.  Use no more than
00781         // 5 lines.
00782         QRect curRect(m_textEdit->rect());
00783         QFontMetrics metrics(fontMetrics());
00784         curRect.setHeight(5 * metrics.lineSpacing());
00785         curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
00786 
00787         QString text(m_textEdit->toPlainText());
00788         curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
00789 
00790         // Scroll bars interfere.  If we don't think there's enough room, enable
00791         // the vertical scrollbar however.
00792         m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00793         if(curRect.height() < m_textEdit->height()) { // then we've got room
00794             m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
00795             m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
00796         }
00797 
00798         m_textEdit->setMinimumSize(curRect.size() + fudge);
00799         m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
00800         updateGeometry();
00801     }
00802 
00803     private:
00804     QPlainTextEdit *m_textEdit;
00805 };
00806 
00807 // Helper function to make the given .desktop file executable by ensuring
00808 // that a #!/usr/bin/env xdg-open line is added if necessary and the file has
00809 // the +x bit set for the user.  Returns false if either fails.
00810 static bool makeFileExecutable(const QString &fileName)
00811 {
00812     // Open the file and read the first two characters, check if it's
00813     // #!.  If not, create a new file, prepend appropriate lines, and copy
00814     // over.
00815     QFile desktopFile(fileName);
00816     if (!desktopFile.open(QFile::ReadOnly)) {
00817         kError(7010) << "Error opening service" << fileName << desktopFile.errorString();
00818         return false;
00819     }
00820 
00821     QByteArray header = desktopFile.peek(2);   // First two chars of file
00822     if (header.size() == 0) {
00823         kError(7010) << "Error inspecting service" << fileName << desktopFile.errorString();
00824         return false; // Some kind of error
00825     }
00826 
00827     if (header != "#!") {
00828         // Add header
00829         KSaveFile saveFile;
00830         saveFile.setFileName(fileName);
00831         if (!saveFile.open()) {
00832             kError(7010) << "Unable to open replacement file for" << fileName << saveFile.errorString();
00833             return false;
00834         }
00835 
00836         QByteArray shebang("#!/usr/bin/env xdg-open\n");
00837         if (saveFile.write(shebang) != shebang.size()) {
00838             kError(7010) << "Error occurred adding header for" << fileName << saveFile.errorString();
00839             saveFile.abort();
00840             return false;
00841         }
00842 
00843         // Now copy the one into the other and then close and reopen desktopFile
00844         QByteArray desktopData(desktopFile.readAll());
00845         if (desktopData.isEmpty()) {
00846             kError(7010) << "Unable to read service" << fileName << desktopFile.errorString();
00847             saveFile.abort();
00848             return false;
00849         }
00850 
00851         if (saveFile.write(desktopData) != desktopData.size()) {
00852             kError(7010) << "Error copying service" << fileName << saveFile.errorString();
00853             saveFile.abort();
00854             return false;
00855         }
00856 
00857         desktopFile.close();
00858         if (!saveFile.finalize()) { // Figures....
00859             kError(7010) << "Error committing changes to service" << fileName << saveFile.errorString();
00860             return false;
00861         }
00862 
00863         if (!desktopFile.open(QFile::ReadOnly)) {
00864             kError(7010) << "Error re-opening service" << fileName << desktopFile.errorString();
00865             return false;
00866         }
00867     } // Add header
00868 
00869     // corresponds to owner on unix, which will have to do since if the user
00870     // isn't the owner we can't change perms anyways.
00871     if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
00872         kError(7010) << "Unable to change permissions for" << fileName << desktopFile.errorString();
00873         return false;
00874     }
00875 
00876     // whew
00877     return true;
00878 }
00879 
00880 // Helper function to make a .desktop file executable if prompted by the user.
00881 // returns true if KRun::run() should continue with execution, false if user declined
00882 // to make the file executable or we failed to make it executable.
00883 static bool makeServiceExecutable(const KService& service, QWidget* window)
00884 {
00885     if (!KAuthorized::authorize("run_desktop_files")) {
00886         kWarning() << "No authorization to execute " << service.entryPath();
00887         KMessageBox::sorry(window, i18n("You are not authorized to execute this service."));
00888         return false; // Don't circumvent the Kiosk
00889     }
00890 
00891     KGuiItem continueItem = KStandardGuiItem::cont();
00892 
00893     SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
00894 
00895     baseDialog->setButtons(KDialog::Ok | KDialog::Cancel);
00896     baseDialog->setButtonGuiItem(KDialog::Ok, continueItem);
00897     baseDialog->setDefaultButton(KDialog::Cancel);
00898     baseDialog->setButtonFocus(KDialog::Cancel);
00899     baseDialog->setCaption(i18nc("Warning about executing unknown .desktop file", "Warning"));
00900 
00901     // Dialog will have explanatory text with a disabled lineedit with the
00902     // Exec= to make it visually distinct.
00903     QWidget *baseWidget = new QWidget(baseDialog);
00904     QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
00905 
00906     QLabel *iconLabel = new QLabel(baseWidget);
00907     QPixmap warningIcon(KIconLoader::global()->loadIcon("dialog-warning", KIconLoader::NoGroup, KIconLoader::SizeHuge));
00908     mainLayout->addWidget(iconLabel);
00909     iconLabel->setPixmap(warningIcon);
00910 
00911     QVBoxLayout *contentLayout = new QVBoxLayout;
00912     QString warningMessage = i18nc("program name follows in a line edit below",
00913                                    "This will start the program:");
00914 
00915     QLabel *message = new QLabel(warningMessage, baseWidget);
00916     contentLayout->addWidget(message);
00917 
00918     // We can use KStandardDirs::findExe to resolve relative pathnames
00919     // but that gets rid of the command line arguments.
00920     QString program = KStandardDirs::realFilePath(service.exec());
00921 
00922     QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
00923     textEdit->setPlainText(program);
00924     textEdit->setReadOnly(true);
00925     contentLayout->addWidget(textEdit);
00926 
00927     QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
00928     contentLayout->addWidget(footerLabel);
00929     contentLayout->addStretch(0); // Don't allow the text edit to expand
00930 
00931     mainLayout->addLayout(contentLayout);
00932 
00933     baseDialog->setMainWidget(baseWidget);
00934     baseDialog->setTextEdit(textEdit);
00935 
00936     // Constrain maximum size.  Minimum size set in
00937     // the dialog's show event.
00938     QSize screenSize = QApplication::desktop()->screen()->size();
00939     baseDialog->resize(screenSize.width() / 4, 50);
00940     baseDialog->setMaximumHeight(screenSize.height() / 3);
00941     baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
00942 
00943     int result = baseDialog->exec();
00944     if (result != KDialog::Accepted) {
00945         return false;
00946     }
00947 
00948     // Assume that service is an absolute path since we're being called (relative paths
00949     // would have been allowed unless Kiosk said no, therefore we already know where the
00950     // .desktop file is.  Now add a header to it if it doesn't already have one
00951     // and add the +x bit.
00952 
00953     if (!::makeFileExecutable(service.entryPath())) {
00954         QString serviceName = service.name();
00955         if(serviceName.isEmpty())
00956             serviceName = service.genericName();
00957 
00958         KMessageBox::sorry(
00959             window,
00960             i18n("Unable to make the service %1 executable, aborting execution", serviceName)
00961         );
00962 
00963         return false;
00964     }
00965 
00966     return true;
00967 }
00968 
00969 bool KRun::run(const KService& _service, const KUrl::List& _urls, QWidget* window,
00970                bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
00971 {
00972     if (!_service.entryPath().isEmpty() &&
00973             !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) &&
00974             !::makeServiceExecutable(_service, window))
00975     {
00976         return false;
00977     }
00978 
00979     if (!tempFiles) {
00980         // Remember we opened those urls, for the "recent documents" menu in kicker
00981         KUrl::List::ConstIterator it = _urls.begin();
00982         for (; it != _urls.end(); ++it) {
00983             //kDebug(7010) << "KRecentDocument::adding " << (*it).url();
00984             KRecentDocument::add(*it, _service.desktopEntryName());
00985         }
00986     }
00987 
00988     if (tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()) {
00989         return runTempService(_service, _urls, window, tempFiles, suggestedFileName, asn);
00990     }
00991 
00992     kDebug(7010) << "KRun::run " << _service.entryPath();
00993 
00994     if (!_urls.isEmpty()) {
00995         kDebug(7010) << "First url " << _urls.first().url();
00996     }
00997 
00998     // Resolve urls if needed, depending on what the app supports
00999     const KUrl::List urls = resolveURLs(_urls, _service);
01000 
01001     QString error;
01002     int pid = 0;
01003 
01004     QByteArray myasn = asn;
01005     // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now
01006     if (window != NULL) {
01007         if (myasn.isEmpty()) {
01008             myasn = KStartupInfo::createNewStartupId();
01009         }
01010         if (myasn != "0") {
01011             KStartupInfoId id;
01012             id.initId(myasn);
01013             KStartupInfoData data;
01014             data.setLaunchedBy(window->winId());
01015             KStartupInfo::sendChange(id, data);
01016         }
01017     }
01018 
01019     int i = KToolInvocation::startServiceByDesktopPath(
01020                 _service.entryPath(), urls.toStringList(), &error, 0L, &pid, myasn
01021             );
01022 
01023     if (i != 0) {
01024         kDebug(7010) << error;
01025         KMessageBox::sorry(window, error);
01026         return false;
01027     }
01028 
01029     kDebug(7010) << "startServiceByDesktopPath worked fine";
01030     return true;
01031 }
01032 
01033 
01034 bool KRun::run(const QString& _exec, const KUrl::List& _urls, QWidget* window, const QString& _name,
01035                const QString& _icon, const QByteArray& asn)
01036 {
01037     KService::Ptr service(new KService(_name, _exec, _icon));
01038 
01039     return run(*service, _urls, window, false, QString(), asn);
01040 }
01041 
01042 bool KRun::runCommand(const QString &cmd, QWidget* window)
01043 {
01044     return runCommand(cmd, window, QString());
01045 }
01046 
01047 bool KRun::runCommand(const QString& cmd, QWidget* window, const QString& workingDirectory)
01048 {
01049     if (cmd.isEmpty()) {
01050         kWarning() << "Command was empty, nothing to run";
01051         return false;
01052     }
01053 
01054     const QStringList args = KShell::splitArgs(cmd);
01055     if (args.isEmpty()) {
01056         kWarning() << "Command could not be parsed.";
01057         return false;
01058     }
01059 
01060     const QString bin = args.first();
01061     return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray(), workingDirectory);
01062 }
01063 
01064 bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName, QWidget* window, const QByteArray& asn)
01065 {
01066     return runCommand(cmd, execName, iconName, window, asn, QString());
01067 }
01068 
01069 bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName,
01070                       QWidget* window, const QByteArray& asn, const QString& workingDirectory)
01071 {
01072     kDebug(7010) << "runCommand " << cmd << "," << execName;
01073     KProcess * proc = new KProcess;
01074     proc->setShellCommand(cmd);
01075     if (!workingDirectory.isEmpty()) {
01076         proc->setWorkingDirectory(workingDirectory);
01077     }
01078     QString bin = binaryName(execName, true);
01079     KService::Ptr service = KService::serviceByDesktopName(bin);
01080     return runCommandInternal(proc, service.data(),
01081                               execName /*executable to check for in slotProcessExited*/,
01082                               execName /*user-visible name*/,
01083                               iconName, window, asn);
01084 }
01085 
01086 KRun::KRun(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
01087            bool showProgressInfo, const QByteArray& asn)
01088         : d(new KRunPrivate(this))
01089 {
01090     d->m_timer.setObjectName("KRun::timer");
01091     d->m_timer.setSingleShot(true);
01092     d->init(url, window, mode, isLocalFile, showProgressInfo, asn);
01093 }
01094 
01095 void KRun::KRunPrivate::init(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
01096                              bool showProgressInfo, const QByteArray& asn)
01097 {
01098     m_bFault = false;
01099     m_bAutoDelete = true;
01100     m_bProgressInfo = showProgressInfo;
01101     m_bFinished = false;
01102     m_job = 0L;
01103     m_strURL = url;
01104     m_bScanFile = false;
01105     m_bIsDirectory = false;
01106     m_bIsLocalFile = isLocalFile;
01107     m_mode = mode;
01108     m_runExecutables = true;
01109     m_window = window;
01110     m_asn = asn;
01111     q->setEnableExternalBrowser(true);
01112 
01113     // Start the timer. This means we will return to the event
01114     // loop and do initialization afterwards.
01115     // Reason: We must complete the constructor before we do anything else.
01116     m_bInit = true;
01117     q->connect(&m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout()));
01118     startTimer();
01119     //kDebug(7010) << "new KRun" << q << url << "timer=" << &m_timer;
01120 
01121     KGlobal::ref();
01122 }
01123 
01124 void KRun::init()
01125 {
01126     kDebug(7010) << "INIT called";
01127     if (!d->m_strURL.isValid()) {
01128         // TODO KDE5: call virtual method on error (see BrowserRun::init)
01129         d->m_showingDialog = true;
01130         KMessageBoxWrapper::error(d->m_window, i18n("Malformed URL\n%1", d->m_strURL.url()));
01131         d->m_showingDialog = false;
01132         d->m_bFault = true;
01133         d->m_bFinished = true;
01134         d->startTimer();
01135         return;
01136     }
01137     if (!KAuthorized::authorizeUrlAction("open", KUrl(), d->m_strURL)) {
01138         QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
01139         d->m_showingDialog = true;
01140         KMessageBoxWrapper::error(d->m_window, msg);
01141         d->m_showingDialog = false;
01142         d->m_bFault = true;
01143         d->m_bFinished = true;
01144         d->startTimer();
01145         return;
01146     }
01147 
01148     if (!d->m_bIsLocalFile && d->m_strURL.isLocalFile()) {
01149         d->m_bIsLocalFile = true;
01150     }
01151 
01152     if (!d->m_externalBrowser.isEmpty() && d->m_strURL.protocol().startsWith(QLatin1String("http"))) {
01153         if (d->runExecutable(d->m_externalBrowser)) {
01154             return;
01155         }
01156     } else if (d->m_bIsLocalFile) {
01157         if (d->m_mode == 0) {
01158             KDE_struct_stat buff;
01159             if (KDE::stat(d->m_strURL.toLocalFile(), &buff) == -1) {
01160                 d->m_showingDialog = true;
01161                 KMessageBoxWrapper::error(d->m_window,
01162                                           i18n("<qt>Unable to run the command specified. "
01163                                           "The file or folder <b>%1</b> does not exist.</qt>" ,
01164                                           Qt::escape(d->m_strURL.prettyUrl())));
01165                 d->m_showingDialog = false;
01166                 d->m_bFault = true;
01167                 d->m_bFinished = true;
01168                 d->startTimer();
01169                 return;
01170             }
01171             d->m_mode = buff.st_mode;
01172         }
01173 
01174         KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL, d->m_mode, true /*local*/);
01175         assert(mime);
01176         kDebug(7010) << "MIME TYPE is " << mime->name();
01177         if (!d->m_externalBrowser.isEmpty() &&
01178             (mime->is(QLatin1String("text/html")) ||
01179              mime->is(QLatin1String("application/xhtml+xml")))) {
01180             if (d->runExecutable(d->m_externalBrowser)) {
01181                 return;
01182             }
01183         } else if (mime->isDefault() && !QFileInfo(d->m_strURL.toLocalFile()).isReadable()) {
01184             // Unknown mimetype because the file is unreadable, no point in showing an open-with dialog (#261002)
01185             const QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
01186             d->m_showingDialog = true;
01187             KMessageBoxWrapper::error(d->m_window, msg);
01188             d->m_showingDialog = false;
01189             d->m_bFault = true;
01190             d->m_bFinished = true;
01191             d->startTimer();
01192             return;
01193         } else {
01194             mimeTypeDetermined(mime->name());
01195             return;
01196         }
01197     }
01198     else if (KProtocolInfo::isHelperProtocol(d->m_strURL)) {
01199         kDebug(7010) << "Helper protocol";
01200         const QString exec = KProtocolInfo::exec(d->m_strURL.protocol());
01201         if (exec.isEmpty()) {
01202             mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
01203             return;
01204         } else {
01205             if (run(exec, KUrl::List() << d->m_strURL, d->m_window, QString(), QString(), d->m_asn)) {
01206                 d->m_bFinished = true;
01207                 d->startTimer();
01208                 return;
01209             }
01210         }
01211     }
01212 
01213     // Did we already get the information that it is a directory ?
01214     if (S_ISDIR(d->m_mode)) {
01215         mimeTypeDetermined("inode/directory");
01216         return;
01217     }
01218 
01219     // Let's see whether it is a directory
01220 
01221     if (!KProtocolManager::supportsListing(d->m_strURL)) {
01222         //kDebug(7010) << "Protocol has no support for listing";
01223         // No support for listing => it can't be a directory (example: http)
01224         scanFile();
01225         return;
01226     }
01227 
01228     kDebug(7010) << "Testing directory (stating)";
01229 
01230     // It may be a directory or a file, let's stat
01231     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
01232     KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags);
01233     job->ui()->setWindow(d->m_window);
01234     connect(job, SIGNAL(result(KJob*)),
01235             this, SLOT(slotStatResult(KJob*)));
01236     d->m_job = job;
01237     kDebug(7010) << " Job " << job << " is about stating " << d->m_strURL.url();
01238 }
01239 
01240 KRun::~KRun()
01241 {
01242     //kDebug(7010) << this;
01243     d->m_timer.stop();
01244     killJob();
01245     KGlobal::deref();
01246     //kDebug(7010) << this << "done";
01247     delete d;
01248 }
01249 
01250 bool KRun::KRunPrivate::runExecutable(const QString& _exec)
01251 {
01252     KUrl::List urls;
01253     urls.append(m_strURL);
01254     if (_exec.startsWith('!')) {
01255         QString exec = _exec.mid(1); // Literal command
01256         exec += " %u";
01257         if (q->run(exec, urls, m_window, QString(), QString(), m_asn)) {
01258             m_bFinished = true;
01259             startTimer();
01260             return true;
01261         }
01262     }
01263     else {
01264         KService::Ptr service = KService::serviceByStorageId(_exec);
01265         if (service && q->run(*service, urls, m_window, false, QString(), m_asn)) {
01266             m_bFinished = true;
01267             startTimer();
01268             return true;
01269         }
01270     }
01271     return false;
01272 }
01273 
01274 void KRun::scanFile()
01275 {
01276     kDebug(7010) << d->m_strURL;
01277     // First, let's check for well-known extensions
01278     // Not when there is a query in the URL, in any case.
01279     if (d->m_strURL.query().isEmpty()) {
01280         KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL);
01281         assert(mime);
01282         if (!mime->isDefault() || d->m_bIsLocalFile) {
01283             kDebug(7010) << "Scanfile: MIME TYPE is " << mime->name();
01284             mimeTypeDetermined(mime->name());
01285             return;
01286         }
01287     }
01288 
01289     // No mimetype found, and the URL is not local  (or fast mode not allowed).
01290     // We need to apply the 'KIO' method, i.e. either asking the server or
01291     // getting some data out of the file, to know what mimetype it is.
01292 
01293     if (!KProtocolManager::supportsReading(d->m_strURL)) {
01294         kError(7010) << "#### NO SUPPORT FOR READING!";
01295         d->m_bFault = true;
01296         d->m_bFinished = true;
01297         d->startTimer();
01298         return;
01299     }
01300     kDebug(7010) << this << " Scanning file " << d->m_strURL.url();
01301 
01302     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
01303     KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
01304     job->ui()->setWindow(d->m_window);
01305     connect(job, SIGNAL(result(KJob*)),
01306             this, SLOT(slotScanFinished(KJob*)));
01307     connect(job, SIGNAL(mimetype(KIO::Job*,QString)),
01308             this, SLOT(slotScanMimeType(KIO::Job*,QString)));
01309     d->m_job = job;
01310     kDebug(7010) << " Job " << job << " is about getting from " << d->m_strURL.url();
01311 }
01312 
01313 // When arriving in that method there are 5 possible states:
01314 // must_init, must_scan_file, found_dir, done+error or done+success.
01315 void KRun::slotTimeout()
01316 {
01317     kDebug(7010) << this << " slotTimeout called";
01318     if (d->m_bInit) {
01319         d->m_bInit = false;
01320         init();
01321         return;
01322     }
01323 
01324     if (d->m_bFault) {
01325         emit error();
01326     }
01327     if (d->m_bFinished) {
01328         emit finished();
01329     }
01330     else {
01331         if (d->m_bScanFile) {
01332             d->m_bScanFile = false;
01333             scanFile();
01334             return;
01335         }
01336         else if (d->m_bIsDirectory) {
01337             d->m_bIsDirectory = false;
01338             mimeTypeDetermined("inode/directory");
01339             return;
01340         }
01341     }
01342 
01343     if (d->m_bAutoDelete) {
01344         deleteLater();
01345         return;
01346     }
01347 }
01348 
01349 void KRun::slotStatResult(KJob * job)
01350 {
01351     d->m_job = 0L;
01352     const int errCode = job->error();
01353     if (errCode) {
01354         // ERR_NO_CONTENT is not an error, but an indication no further
01355         // actions needs to be taken.
01356         if (errCode != KIO::ERR_NO_CONTENT) {
01357             d->m_showingDialog = true;
01358             kError(7010) << this << "ERROR" << job->error() << job->errorString();
01359             job->uiDelegate()->showErrorMessage();
01360             //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
01361             d->m_showingDialog = false;
01362             d->m_bFault = true;
01363         }
01364 
01365         d->m_bFinished = true;
01366 
01367         // will emit the error and autodelete this
01368         d->startTimer();
01369     }
01370     else {
01371         kDebug(7010) << "Finished";
01372 
01373         KIO::StatJob* statJob = qobject_cast<KIO::StatJob*>(job);
01374         if (!statJob) {
01375             kFatal() << "job is a " << typeid(*job).name() << " should be a StatJob";
01376         }
01377 
01378         // Update our URL in case of a redirection
01379         setUrl(statJob->url());
01380 
01381         const KIO::UDSEntry entry = statJob->statResult();
01382         const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
01383         if (S_ISDIR(mode)) {
01384             d->m_bIsDirectory = true; // it's a dir
01385         }
01386         else {
01387             d->m_bScanFile = true; // it's a file
01388         }
01389 
01390         d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
01391 
01392         // mimetype already known? (e.g. print:/manager)
01393         const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE) ;
01394 
01395         if (!knownMimeType.isEmpty()) {
01396             mimeTypeDetermined(knownMimeType);
01397             d->m_bFinished = true;
01398         }
01399 
01400         // We should have found something
01401         assert(d->m_bScanFile || d->m_bIsDirectory);
01402 
01403         // Start the timer. Once we get the timer event this
01404         // protocol server is back in the pool and we can reuse it.
01405         // This gives better performance than starting a new slave
01406         d->startTimer();
01407     }
01408 }
01409 
01410 void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
01411 {
01412     if (mimetype.isEmpty()) {
01413         kWarning(7010) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().protocol();
01414     }
01415     mimeTypeDetermined(mimetype);
01416     d->m_job = 0;
01417 }
01418 
01419 void KRun::slotScanFinished(KJob *job)
01420 {
01421     d->m_job = 0;
01422     const int errCode = job->error();
01423     if (errCode) {
01424         // ERR_NO_CONTENT is not an error, but an indication no further
01425         // actions needs to be taken.
01426         if (errCode != KIO::ERR_NO_CONTENT) {
01427             d->m_showingDialog = true;
01428             kError(7010) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
01429             job->uiDelegate()->showErrorMessage();
01430             //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
01431             d->m_showingDialog = false;
01432 
01433             d->m_bFault = true;
01434         }
01435 
01436         d->m_bFinished = true;
01437         // will emit the error and autodelete this
01438         d->startTimer();
01439     }
01440 }
01441 
01442 void KRun::mimeTypeDetermined(const QString& mimeType)
01443 {
01444     // foundMimeType reimplementations might show a dialog box;
01445     // make sure some timer doesn't kill us meanwhile (#137678, #156447)
01446     Q_ASSERT(!d->m_showingDialog);
01447     d->m_showingDialog = true;
01448 
01449     foundMimeType(mimeType);
01450 
01451     d->m_showingDialog = false;
01452 
01453     // We cannot assume that we're finished here. Some reimplementations
01454     // start a KIO job and call setFinished only later.
01455 }
01456 
01457 void KRun::foundMimeType(const QString& type)
01458 {
01459     kDebug(7010) << "Resulting mime type is " << type;
01460 
01461     KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
01462     if (job) {
01463         // Update our URL in case of a redirection
01464         setUrl( job->url() );
01465 
01466         job->putOnHold();
01467         KIO::Scheduler::publishSlaveOnHold();
01468         d->m_job = 0;
01469     }
01470 
01471     Q_ASSERT(!d->m_bFinished);
01472 
01473     // Support for preferred service setting, see setPreferredService
01474     if (!d->m_preferredService.isEmpty()) {
01475         kDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService;
01476         KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
01477         if (serv && serv->hasMimeType(type)) {
01478             KUrl::List lst;
01479             lst.append(d->m_strURL);
01480             if (KRun::run(*serv, lst, d->m_window, false, QString(), d->m_asn)) {
01481                 setFinished(true);
01482                 return;
01483             }
01488         }
01489     }
01490 
01491     // Resolve .desktop files from media:/, remote:/, applications:/ etc.
01492     KMimeType::Ptr mime = KMimeType::mimeType(type, KMimeType::ResolveAliases);
01493     if (!mime) {
01494         kWarning(7010) << "Unknown mimetype " << type;
01495     }
01496     if (mime && mime->is("application/x-desktop") && !d->m_localPath.isEmpty()) {
01497         d->m_strURL = KUrl();
01498         d->m_strURL.setPath(d->m_localPath);
01499     }
01500 
01501     if (!KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) {
01502         d->m_bFault = true;
01503     }
01504     setFinished(true);
01505 }
01506 
01507 void KRun::killJob()
01508 {
01509     if (d->m_job) {
01510         kDebug(7010) << this << "m_job=" << d->m_job;
01511         d->m_job->kill();
01512         d->m_job = 0L;
01513     }
01514 }
01515 
01516 void KRun::abort()
01517 {
01518     if (d->m_bFinished) {
01519         return;
01520     }
01521     kDebug(7010) << this << "m_showingDialog=" << d->m_showingDialog;
01522     killJob();
01523     // If we're showing an error message box, the rest will be done
01524     // after closing the msgbox -> don't autodelete nor emit signals now.
01525     if (d->m_showingDialog) {
01526         return;
01527     }
01528     d->m_bFault = true;
01529     d->m_bFinished = true;
01530     d->m_bInit = false;
01531     d->m_bScanFile = false;
01532 
01533     // will emit the error and autodelete this
01534     d->startTimer();
01535 }
01536 
01537 QWidget* KRun::window() const
01538 {
01539     return d->m_window;
01540 }
01541 
01542 bool KRun::hasError() const
01543 {
01544     return d->m_bFault;
01545 }
01546 
01547 bool KRun::hasFinished() const
01548 {
01549     return d->m_bFinished;
01550 }
01551 
01552 bool KRun::autoDelete() const
01553 {
01554     return d->m_bAutoDelete;
01555 }
01556 
01557 void KRun::setAutoDelete(bool b)
01558 {
01559     d->m_bAutoDelete = b;
01560 }
01561 
01562 void KRun::setEnableExternalBrowser(bool b)
01563 {
01564     if (b) {
01565         d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
01566     }
01567     else {
01568         d->m_externalBrowser.clear();
01569     }
01570 }
01571 
01572 void KRun::setPreferredService(const QString& desktopEntryName)
01573 {
01574     d->m_preferredService = desktopEntryName;
01575 }
01576 
01577 void KRun::setRunExecutables(bool b)
01578 {
01579     d->m_runExecutables = b;
01580 }
01581 
01582 void KRun::setSuggestedFileName(const QString& fileName)
01583 {
01584     d->m_suggestedFileName = fileName;
01585 }
01586 
01587 QString KRun::suggestedFileName() const
01588 {
01589     return d->m_suggestedFileName;
01590 }
01591 
01592 bool KRun::isExecutable(const QString& serviceType)
01593 {
01594     return (serviceType == "application/x-desktop" ||
01595             serviceType == "application/x-executable" ||
01596             serviceType == "application/x-ms-dos-executable" ||
01597             serviceType == "application/x-shellscript");
01598 }
01599 
01600 void KRun::setUrl(const KUrl &url)
01601 {
01602     d->m_strURL = url;
01603 }
01604 
01605 KUrl KRun::url() const
01606 {
01607     return d->m_strURL;
01608 }
01609 
01610 void KRun::setError(bool error)
01611 {
01612     d->m_bFault = error;
01613 }
01614 
01615 void KRun::setProgressInfo(bool progressInfo)
01616 {
01617     d->m_bProgressInfo = progressInfo;
01618 }
01619 
01620 bool KRun::progressInfo() const
01621 {
01622     return d->m_bProgressInfo;
01623 }
01624 
01625 void KRun::setFinished(bool finished)
01626 {
01627     d->m_bFinished = finished;
01628     if (finished)
01629         d->startTimer();
01630 }
01631 
01632 void KRun::setJob(KIO::Job *job)
01633 {
01634     d->m_job = job;
01635 }
01636 
01637 KIO::Job* KRun::job()
01638 {
01639     return d->m_job;
01640 }
01641 
01642 #ifndef KDE_NO_DEPRECATED
01643 QTimer& KRun::timer()
01644 {
01645     return d->m_timer;
01646 }
01647 #endif
01648 
01649 #ifndef KDE_NO_DEPRECATED
01650 void KRun::setDoScanFile(bool scanFile)
01651 {
01652     d->m_bScanFile = scanFile;
01653 }
01654 #endif
01655 
01656 #ifndef KDE_NO_DEPRECATED
01657 bool KRun::doScanFile() const
01658 {
01659     return d->m_bScanFile;
01660 }
01661 #endif
01662 
01663 #ifndef KDE_NO_DEPRECATED
01664 void KRun::setIsDirecory(bool isDirectory)
01665 {
01666     d->m_bIsDirectory = isDirectory;
01667 }
01668 #endif
01669 
01670 bool KRun::isDirectory() const
01671 {
01672     return d->m_bIsDirectory;
01673 }
01674 
01675 #ifndef KDE_NO_DEPRECATED
01676 void KRun::setInitializeNextAction(bool initialize)
01677 {
01678     d->m_bInit = initialize;
01679 }
01680 #endif
01681 
01682 #ifndef KDE_NO_DEPRECATED
01683 bool KRun::initializeNextAction() const
01684 {
01685     return d->m_bInit;
01686 }
01687 #endif
01688 
01689 void KRun::setIsLocalFile(bool isLocalFile)
01690 {
01691     d->m_bIsLocalFile = isLocalFile;
01692 }
01693 
01694 bool KRun::isLocalFile() const
01695 {
01696     return d->m_bIsLocalFile;
01697 }
01698 
01699 void KRun::setMode(mode_t mode)
01700 {
01701     d->m_mode = mode;
01702 }
01703 
01704 mode_t KRun::mode() const
01705 {
01706     return d->m_mode;
01707 }
01708 
01709 /****************/
01710 
01711 #ifndef Q_WS_X11
01712 int KProcessRunner::run(KProcess * p, const QString & executable)
01713 {
01714     return (new KProcessRunner(p, executable))->pid();
01715 }
01716 #else
01717 int KProcessRunner::run(KProcess * p, const QString & executable, const KStartupInfoId& id)
01718 {
01719     return (new KProcessRunner(p, executable, id))->pid();
01720 }
01721 #endif
01722 
01723 #ifndef Q_WS_X11
01724 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable)
01725 #else
01726 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable, const KStartupInfoId& _id) :
01727         id(_id)
01728 #endif
01729 {
01730     m_pid = 0;
01731     process = p;
01732     m_executable = executable;
01733     connect(process, SIGNAL(finished(int,QProcess::ExitStatus)),
01734             this, SLOT(slotProcessExited(int,QProcess::ExitStatus)));
01735 
01736     process->start();
01737     if (!process->waitForStarted()) {
01738         //kDebug() << "wait for started failed, exitCode=" << process->exitCode()
01739         //         << "exitStatus=" << process->exitStatus();
01740         // Note that exitCode is 255 here (the first time), and 0 later on (bug?).
01741         slotProcessExited(255, process->exitStatus());
01742     }
01743     else {
01744 #ifdef Q_WS_X11
01745         m_pid = process->pid();
01746 #endif
01747     }
01748 }
01749 
01750 KProcessRunner::~KProcessRunner()
01751 {
01752     delete process;
01753 }
01754 
01755 int KProcessRunner::pid() const
01756 {
01757     return m_pid;
01758 }
01759 
01760 void KProcessRunner::terminateStartupNotification()
01761 {
01762 #ifdef Q_WS_X11
01763     if (!id.none()) {
01764         KStartupInfoData data;
01765         data.addPid(m_pid); // announce this pid for the startup notification has finished
01766         data.setHostname();
01767         KStartupInfo::sendFinish(id, data);
01768     }
01769 #endif
01770 
01771 }
01772 
01773 void
01774 KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
01775 {
01776     kDebug(7010) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus;
01777     Q_UNUSED(exitStatus);
01778 
01779     terminateStartupNotification(); // do this before the messagebox
01780     if (exitCode != 0 && !m_executable.isEmpty()) {
01781         // Let's see if the error is because the exe doesn't exist.
01782         // When this happens, waitForStarted returns false, but not if kioexec
01783         // was involved, then we come here, that's why the code is here.
01784         //
01785         // We'll try to find the executable relatively to current directory,
01786         // (or with a full path, if m_executable is absolute), and then in the PATH.
01787         if (!QFile(m_executable).exists() && KStandardDirs::findExe(m_executable).isEmpty()) {
01788             KGlobal::ref();
01789             KMessageBox::sorry(0L, i18n("Could not find the program '%1'", m_executable));
01790             KGlobal::deref();
01791         }
01792         else {
01793             kDebug() << process->readAllStandardError();
01794         }
01795     }
01796     deleteLater();
01797 }
01798 
01799 #include "krun.moc"
01800 #include "krun_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:01 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.9.5 API Reference

Skip menu "kdelibs-4.9.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal