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