• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • 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)); // 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     QString exec;
01156     if (d->m_strURL.protocol().startsWith(QLatin1String("http"))) {
01157         exec = d->m_externalBrowser;
01158     }
01159 
01160     if (d->m_bIsLocalFile) {
01161         if (d->m_mode == 0) {
01162             KDE_struct_stat buff;
01163             if (KDE::stat(d->m_strURL.path(), &buff) == -1) {
01164                 d->m_showingDialog = true;
01165                 KMessageBoxWrapper::error(d->m_window,
01166                                           i18n("<qt>Unable to run the command specified. "
01167                                           "The file or folder <b>%1</b> does not exist.</qt>" ,
01168                                           Qt::escape(d->m_strURL.prettyUrl())));
01169                 d->m_showingDialog = false;
01170                 d->m_bFault = true;
01171                 d->m_bFinished = true;
01172                 d->startTimer();
01173                 return;
01174             }
01175             d->m_mode = buff.st_mode;
01176         }
01177 
01178         KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL, d->m_mode, d->m_bIsLocalFile);
01179         assert(mime);
01180         kDebug(7010) << "MIME TYPE is " << mime->name();
01181         mimeTypeDetermined(mime->name());
01182         return;
01183     }
01184     else if (!exec.isEmpty() || KProtocolInfo::isHelperProtocol(d->m_strURL)) {
01185         kDebug(7010) << "Helper protocol";
01186 
01187         bool ok = false;
01188         KUrl::List urls;
01189         urls.append(d->m_strURL);
01190         if (exec.isEmpty()) {
01191             exec = KProtocolInfo::exec(d->m_strURL.protocol());
01192             if (exec.isEmpty()) {
01193                 mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
01194                 return;
01195             }
01196             run(exec, urls, d->m_window, QString(), QString(), d->m_asn);
01197             ok = true;
01198         }
01199         else if (exec.startsWith('!')) {
01200             exec = exec.mid(1); // Literal command
01201             exec += " %u";
01202             run(exec, urls, d->m_window, QString(), QString(), d->m_asn);
01203             ok = true;
01204         }
01205         else {
01206             KService::Ptr service = KService::serviceByStorageId(exec);
01207             if (service) {
01208                 run(*service, urls, d->m_window, false, QString(), d->m_asn);
01209                 ok = true;
01210             }
01211         }
01212 
01213         if (ok) {
01214             d->m_bFinished = true;
01215             // will emit the error and autodelete this
01216             d->startTimer();
01217             return;
01218         }
01219     }
01220 
01221     // Did we already get the information that it is a directory ?
01222     if (S_ISDIR(d->m_mode)) {
01223         mimeTypeDetermined("inode/directory");
01224         return;
01225     }
01226 
01227     // Let's see whether it is a directory
01228 
01229     if (!KProtocolManager::supportsListing(d->m_strURL)) {
01230         //kDebug(7010) << "Protocol has no support for listing";
01231         // No support for listing => it can't be a directory (example: http)
01232         scanFile();
01233         return;
01234     }
01235 
01236     kDebug(7010) << "Testing directory (stating)";
01237 
01238     // It may be a directory or a file, let's stat
01239     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
01240     KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags);
01241     job->ui()->setWindow(d->m_window);
01242     connect(job, SIGNAL(result(KJob *)),
01243             this, SLOT(slotStatResult(KJob *)));
01244     d->m_job = job;
01245     kDebug(7010) << " Job " << job << " is about stating " << d->m_strURL.url();
01246 }
01247 
01248 KRun::~KRun()
01249 {
01250     //kDebug(7010) << this;
01251     d->m_timer.stop();
01252     killJob();
01253     KGlobal::deref();
01254     //kDebug(7010) << this << "done";
01255     delete d;
01256 }
01257 
01258 void KRun::scanFile()
01259 {
01260     kDebug(7010) << d->m_strURL;
01261     // First, let's check for well-known extensions
01262     // Not when there is a query in the URL, in any case.
01263     if (d->m_strURL.query().isEmpty()) {
01264         KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL);
01265         assert(mime);
01266         if (!mime->isDefault() || d->m_bIsLocalFile) {
01267             kDebug(7010) << "Scanfile: MIME TYPE is " << mime->name();
01268             mimeTypeDetermined(mime->name());
01269             return;
01270         }
01271     }
01272 
01273     // No mimetype found, and the URL is not local  (or fast mode not allowed).
01274     // We need to apply the 'KIO' method, i.e. either asking the server or
01275     // getting some data out of the file, to know what mimetype it is.
01276 
01277     if (!KProtocolManager::supportsReading(d->m_strURL)) {
01278         kError(7010) << "#### NO SUPPORT FOR READING!";
01279         d->m_bFault = true;
01280         d->m_bFinished = true;
01281         d->startTimer();
01282         return;
01283     }
01284     kDebug(7010) << this << " Scanning file " << d->m_strURL.url();
01285 
01286     KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
01287     KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
01288     job->ui()->setWindow(d->m_window);
01289     connect(job, SIGNAL(result(KJob *)),
01290             this, SLOT(slotScanFinished(KJob *)));
01291     connect(job, SIGNAL(mimetype(KIO::Job *, const QString &)),
01292             this, SLOT(slotScanMimeType(KIO::Job *, const QString &)));
01293     d->m_job = job;
01294     kDebug(7010) << " Job " << job << " is about getting from " << d->m_strURL.url();
01295 }
01296 
01297 // When arriving in that method there are 5 possible states:
01298 // must_init, must_scan_file, found_dir, done+error or done+success.
01299 void KRun::slotTimeout()
01300 {
01301     kDebug(7010) << this << " slotTimeout called";
01302     if (d->m_bInit) {
01303         d->m_bInit = false;
01304         init();
01305         return;
01306     }
01307 
01308     if (d->m_bFault) {
01309         emit error();
01310     }
01311     if (d->m_bFinished) {
01312         emit finished();
01313     }
01314     else {
01315         if (d->m_bScanFile) {
01316             d->m_bScanFile = false;
01317             scanFile();
01318             return;
01319         }
01320         else if (d->m_bIsDirectory) {
01321             d->m_bIsDirectory = false;
01322             mimeTypeDetermined("inode/directory");
01323             return;
01324         }
01325     }
01326 
01327     if (d->m_bAutoDelete) {
01328         deleteLater();
01329         return;
01330     }
01331 }
01332 
01333 void KRun::slotStatResult(KJob * job)
01334 {
01335     d->m_job = 0L;
01336     if (job->error()) {
01337         d->m_showingDialog = true;
01338         kError(7010) << this << "ERROR" << job->error() << job->errorString();
01339         job->uiDelegate()->showErrorMessage();
01340         //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
01341         d->m_showingDialog = false;
01342 
01343         d->m_bFault = true;
01344         d->m_bFinished = true;
01345 
01346         // will emit the error and autodelete this
01347         d->startTimer();
01348 
01349     }
01350     else {
01351 
01352         kDebug(7010) << "Finished";
01353         if (!qobject_cast<KIO::StatJob*>(job)) {
01354             kFatal() << "job is a " << typeid(*job).name() << " should be a StatJob";
01355         }
01356 
01357         const KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
01358         const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
01359         if (S_ISDIR(mode)) {
01360             d->m_bIsDirectory = true; // it's a dir
01361         }
01362         else {
01363             d->m_bScanFile = true; // it's a file
01364         }
01365 
01366         d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
01367 
01368         // mimetype already known? (e.g. print:/manager)
01369         const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE) ;
01370 
01371         if (!knownMimeType.isEmpty()) {
01372             mimeTypeDetermined(knownMimeType);
01373             d->m_bFinished = true;
01374         }
01375 
01376         // We should have found something
01377         assert(d->m_bScanFile || d->m_bIsDirectory);
01378 
01379         // Start the timer. Once we get the timer event this
01380         // protocol server is back in the pool and we can reuse it.
01381         // This gives better performance than starting a new slave
01382         d->startTimer();
01383     }
01384 }
01385 
01386 void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
01387 {
01388     if (mimetype.isEmpty()) {
01389         kWarning(7010) << "get() didn't emit a mimetype! Probably a kioslave bug, please check the implementation of" << url().protocol();
01390     }
01391     mimeTypeDetermined(mimetype);
01392     d->m_job = 0;
01393 }
01394 
01395 void KRun::slotScanFinished(KJob *job)
01396 {
01397     d->m_job = 0;
01398     if (job->error()) {
01399         d->m_showingDialog = true;
01400         kError(7010) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
01401         job->uiDelegate()->showErrorMessage();
01402         //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
01403         d->m_showingDialog = false;
01404 
01405         d->m_bFault = true;
01406         d->m_bFinished = true;
01407 
01408         // will emit the error and autodelete this
01409         d->startTimer();
01410     }
01411 }
01412 
01413 void KRun::mimeTypeDetermined(const QString& mimeType)
01414 {
01415     // foundMimeType reimplementations might show a dialog box;
01416     // make sure some timer doesn't kill us meanwhile (#137678, #156447)
01417     Q_ASSERT(!d->m_showingDialog);
01418     d->m_showingDialog = true;
01419 
01420     foundMimeType(mimeType);
01421 
01422     d->m_showingDialog = false;
01423 
01424     // We cannot assume that we're finished here. Some reimplementations
01425     // start a KIO job and call setFinished only later.
01426 }
01427 
01428 void KRun::foundMimeType(const QString& type)
01429 {
01430     kDebug(7010) << "Resulting mime type is " << type;
01431 
01432     KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
01433     if (job) {
01434         // Update our URL in case of a redirection
01435         setUrl( job->url() );
01436 
01437         job->putOnHold();
01438         KIO::Scheduler::publishSlaveOnHold();
01439         d->m_job = 0;
01440     }
01441 
01442     Q_ASSERT(!d->m_bFinished);
01443 
01444     // Support for preferred service setting, see setPreferredService
01445     if (!d->m_preferredService.isEmpty()) {
01446         kDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService;
01447         KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
01448         if (serv && serv->hasMimeType(type)) {
01449             KUrl::List lst;
01450             lst.append(d->m_strURL);
01451             if (KRun::run(*serv, lst, d->m_window, false, QString(), d->m_asn)) {
01452                 setFinished(true);
01453                 return;
01454             }
01459         }
01460     }
01461 
01462     // Resolve .desktop files from media:/, remote:/, applications:/ etc.
01463     KMimeType::Ptr mime = KMimeType::mimeType(type, KMimeType::ResolveAliases);
01464     if (!mime) {
01465         kWarning(7010) << "Unknown mimetype " << type;
01466     }
01467     if (mime && mime->is("application/x-desktop") && !d->m_localPath.isEmpty()) {
01468         d->m_strURL = KUrl();
01469         d->m_strURL.setPath(d->m_localPath);
01470     }
01471 
01472     if (!KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) {
01473         d->m_bFault = true;
01474     }
01475     setFinished(true);
01476 }
01477 
01478 void KRun::killJob()
01479 {
01480     if (d->m_job) {
01481         kDebug(7010) << this << "m_job=" << d->m_job;
01482         d->m_job->kill();
01483         d->m_job = 0L;
01484     }
01485 }
01486 
01487 void KRun::abort()
01488 {
01489     if (d->m_bFinished) {
01490         return;
01491     }
01492     kDebug(7010) << this << "m_showingDialog=" << d->m_showingDialog;
01493     killJob();
01494     // If we're showing an error message box, the rest will be done
01495     // after closing the msgbox -> don't autodelete nor emit signals now.
01496     if (d->m_showingDialog) {
01497         return;
01498     }
01499     d->m_bFault = true;
01500     d->m_bFinished = true;
01501     d->m_bInit = false;
01502     d->m_bScanFile = false;
01503 
01504     // will emit the error and autodelete this
01505     d->startTimer();
01506 }
01507 
01508 bool KRun::hasError() const
01509 {
01510     return d->m_bFault;
01511 }
01512 
01513 bool KRun::hasFinished() const
01514 {
01515     return d->m_bFinished;
01516 }
01517 
01518 bool KRun::autoDelete() const
01519 {
01520     return d->m_bAutoDelete;
01521 }
01522 
01523 void KRun::setAutoDelete(bool b)
01524 {
01525     d->m_bAutoDelete = b;
01526 }
01527 
01528 void KRun::setEnableExternalBrowser(bool b)
01529 {
01530     if (b) {
01531         d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
01532     }
01533     else {
01534         d->m_externalBrowser.clear();
01535     }
01536 }
01537 
01538 void KRun::setPreferredService(const QString& desktopEntryName)
01539 {
01540     d->m_preferredService = desktopEntryName;
01541 }
01542 
01543 void KRun::setRunExecutables(bool b)
01544 {
01545     d->m_runExecutables = b;
01546 }
01547 
01548 void KRun::setSuggestedFileName(const QString& fileName)
01549 {
01550     d->m_suggestedFileName = fileName;
01551 }
01552 
01553 QString KRun::suggestedFileName() const
01554 {
01555     return d->m_suggestedFileName;
01556 }
01557 
01558 bool KRun::isExecutable(const QString& serviceType)
01559 {
01560     return (serviceType == "application/x-desktop" ||
01561             serviceType == "application/x-executable" ||
01562             serviceType == "application/x-ms-dos-executable" ||
01563             serviceType == "application/x-shellscript");
01564 }
01565 
01566 void KRun::setUrl(const KUrl &url)
01567 {
01568     d->m_strURL = url;
01569 }
01570 
01571 KUrl KRun::url() const
01572 {
01573     return d->m_strURL;
01574 }
01575 
01576 void KRun::setError(bool error)
01577 {
01578     d->m_bFault = error;
01579 }
01580 
01581 void KRun::setProgressInfo(bool progressInfo)
01582 {
01583     d->m_bProgressInfo = progressInfo;
01584 }
01585 
01586 bool KRun::progressInfo() const
01587 {
01588     return d->m_bProgressInfo;
01589 }
01590 
01591 void KRun::setFinished(bool finished)
01592 {
01593     d->m_bFinished = finished;
01594     if (finished)
01595         d->startTimer();
01596 }
01597 
01598 void KRun::setJob(KIO::Job *job)
01599 {
01600     d->m_job = job;
01601 }
01602 
01603 KIO::Job* KRun::job()
01604 {
01605     return d->m_job;
01606 }
01607 
01608 #ifndef KDE_NO_DEPRECATED
01609 QTimer& KRun::timer()
01610 {
01611     return d->m_timer;
01612 }
01613 #endif
01614 
01615 #ifndef KDE_NO_DEPRECATED
01616 void KRun::setDoScanFile(bool scanFile)
01617 {
01618     d->m_bScanFile = scanFile;
01619 }
01620 #endif
01621 
01622 #ifndef KDE_NO_DEPRECATED
01623 bool KRun::doScanFile() const
01624 {
01625     return d->m_bScanFile;
01626 }
01627 #endif
01628 
01629 #ifndef KDE_NO_DEPRECATED
01630 void KRun::setIsDirecory(bool isDirectory)
01631 {
01632     d->m_bIsDirectory = isDirectory;
01633 }
01634 #endif
01635 
01636 bool KRun::isDirectory() const
01637 {
01638     return d->m_bIsDirectory;
01639 }
01640 
01641 #ifndef KDE_NO_DEPRECATED
01642 void KRun::setInitializeNextAction(bool initialize)
01643 {
01644     d->m_bInit = initialize;
01645 }
01646 #endif
01647 
01648 #ifndef KDE_NO_DEPRECATED
01649 bool KRun::initializeNextAction() const
01650 {
01651     return d->m_bInit;
01652 }
01653 #endif
01654 
01655 void KRun::setIsLocalFile(bool isLocalFile)
01656 {
01657     d->m_bIsLocalFile = isLocalFile;
01658 }
01659 
01660 bool KRun::isLocalFile() const
01661 {
01662     return d->m_bIsLocalFile;
01663 }
01664 
01665 void KRun::setMode(mode_t mode)
01666 {
01667     d->m_mode = mode;
01668 }
01669 
01670 mode_t KRun::mode() const
01671 {
01672     return d->m_mode;
01673 }
01674 
01675 /****************/
01676 
01677 #ifndef Q_WS_X11
01678 int KProcessRunner::run(KProcess * p, const QString & executable)
01679 {
01680     return (new KProcessRunner(p, executable))->pid();
01681 }
01682 #else
01683 int KProcessRunner::run(KProcess * p, const QString & executable, const KStartupInfoId& id)
01684 {
01685     return (new KProcessRunner(p, executable, id))->pid();
01686 }
01687 #endif
01688 
01689 #ifndef Q_WS_X11
01690 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable)
01691 #else
01692 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable, const KStartupInfoId& _id) :
01693         id(_id)
01694 #endif
01695 {
01696     m_pid = 0;
01697     process = p;
01698     m_executable = executable;
01699     connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
01700             this, SLOT(slotProcessExited(int, QProcess::ExitStatus)));
01701 
01702     process->start();
01703     if (!process->waitForStarted()) {
01704         //kDebug() << "wait for started failed, exitCode=" << process->exitCode()
01705         //         << "exitStatus=" << process->exitStatus();
01706         // Note that exitCode is 255 here (the first time), and 0 later on (bug?).
01707         slotProcessExited(255, process->exitStatus());
01708     }
01709     else {
01710 #ifdef Q_WS_X11
01711         m_pid = process->pid();
01712 #endif
01713     }
01714 }
01715 
01716 KProcessRunner::~KProcessRunner()
01717 {
01718     delete process;
01719 }
01720 
01721 int KProcessRunner::pid() const
01722 {
01723     return m_pid;
01724 }
01725 
01726 void KProcessRunner::terminateStartupNotification()
01727 {
01728 #ifdef Q_WS_X11
01729     if (!id.none()) {
01730         KStartupInfoData data;
01731         data.addPid(m_pid); // announce this pid for the startup notification has finished
01732         data.setHostname();
01733         KStartupInfo::sendFinish(id, data);
01734     }
01735 #endif
01736 
01737 }
01738 
01739 void
01740 KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
01741 {
01742     kDebug(7010) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus;
01743     Q_UNUSED(exitStatus);
01744 
01745     terminateStartupNotification(); // do this before the messagebox
01746     if (exitCode != 0 && !m_executable.isEmpty()) {
01747         // Let's see if the error is because the exe doesn't exist.
01748         // When this happens, waitForStarted returns false, but not if kioexec
01749         // was involved, then we come here, that's why the code is here.
01750         //
01751         // We'll try to find the executable relatively to current directory,
01752         // (or with a full path, if m_executable is absolute), and then in the PATH.
01753         if (!QFile(m_executable).exists() && KStandardDirs::findExe(m_executable).isEmpty()) {
01754             KGlobal::ref();
01755             KMessageBox::sorry(0L, i18n("Could not find the program '%1'", m_executable));
01756             KGlobal::deref();
01757         }
01758         else {
01759             kDebug() << process->readAllStandardError();
01760         }
01761     }
01762     deleteLater();
01763 }
01764 
01765 #include "krun.moc"
01766 #include "krun_p.moc"

KIO

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • 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
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal