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