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 Thu May 10 2012 20:55:22 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu May 10 2012 20:55:22 by doxygen 1.8.0 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.