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

Skip menu "kdelibs-4.8.3 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