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