fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kio / kio / krun.cpp
blobc57eb221bf1059fab54dcc01c3c20f6eb1cb4a53
1 /* This file is part of the KDE libraries
2 Copyright (C) 2000 Torben Weis <weis@kde.org>
3 Copyright (C) 2006 David Faure <faure@kde.org>
4 Copyright (C) 2009 Michael Pyne <michael.pyne@kdemail.net>
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
22 #include "krun.h"
23 #include "krun_p.h"
25 #include <config.h>
27 #include <assert.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <typeinfo>
32 #include <sys/stat.h>
34 #include <QtGui/QWidget>
35 #include <QtGui/QLabel>
36 #include <QtGui/QVBoxLayout>
37 #include <QtGui/QHBoxLayout>
38 #include <QtGui/QPlainTextEdit>
39 #include <QtGui/QApplication>
40 #include <QtGui/QDesktopWidget>
42 #include "kmimetypetrader.h"
43 #include "kmimetype.h"
44 #include "kio/jobclasses.h" // for KIO::JobFlags
45 #include "kio/job.h"
46 #include "kio/jobuidelegate.h"
47 #include "kio/global.h"
48 #include "kio/scheduler.h"
49 #include "kio/netaccess.h"
50 #include "kfile/kopenwithdialog.h"
51 #include "kfile/krecentdocument.h"
52 #include "kdesktopfileactions.h"
54 #include <kauthorized.h>
55 #include <kmessageboxwrapper.h>
56 #include <kurl.h>
57 #include <kglobal.h>
58 #include <ktoolinvocation.h>
59 #include <kauthorized.h>
60 #include <kdebug.h>
61 #include <klocale.h>
62 #include <kprotocolmanager.h>
63 #include <kstandarddirs.h>
64 #include <kprocess.h>
65 #include <QtCore/QFile>
66 #include <QtCore/QFileInfo>
67 #include <QtCore/QTextIStream>
68 #include <QtCore/QDate>
69 #include <QtCore/QRegExp>
70 #include <kdesktopfile.h>
71 #include <kmacroexpander.h>
72 #include <kshell.h>
73 #include <QTextDocument>
74 #include <kde_file.h>
75 #include <kconfiggroup.h>
76 #include <kdialog.h>
77 #include <kstandardguiitem.h>
78 #include <kguiitem.h>
79 #include <ksavefile.h>
81 #ifdef Q_WS_X11
82 #include <kwindowsystem.h>
83 #endif
85 KRun::KRunPrivate::KRunPrivate(KRun *parent)
86 : q(parent),
87 m_showingDialog(false)
91 void KRun::KRunPrivate::startTimer()
93 m_timer.start(0);
96 // ---------------------------------------------------------------------------
98 bool KRun::isExecutableFile(const KUrl& url, const QString &mimetype)
100 if (!url.isLocalFile()) {
101 return false;
103 QFileInfo file(url.toLocalFile());
104 if (file.isExecutable()) { // Got a prospective file to run
105 KMimeType::Ptr mimeType = KMimeType::mimeType(mimetype, KMimeType::ResolveAliases);
106 if (mimeType && (mimeType->is(QLatin1String("application/x-executable")) ||
107 #ifdef Q_WS_WIN
108 mimeType->is(QLatin1String("application/x-ms-dos-executable")) ||
109 #endif
110 mimeType->is(QLatin1String("application/x-executable-script")))
113 return true;
116 return false;
119 // This is called by foundMimeType, since it knows the mimetype of the URL
120 bool KRun::runUrl(const KUrl& u, const QString& _mimetype, QWidget* window, bool tempFile, bool runExecutables, const QString& suggestedFileName, const QByteArray& asn)
122 bool noRun = false;
123 bool noAuth = false;
124 if (_mimetype == QLatin1String("inode/directory-locked")) {
125 KMessageBoxWrapper::error(window,
126 i18n("<qt>Unable to enter <b>%1</b>.\nYou do not have access rights to this location.</qt>", Qt::escape(u.prettyUrl())));
127 return false;
129 else if (_mimetype == QLatin1String("application/x-desktop")) {
130 if (u.isLocalFile() && runExecutables) {
131 return KDesktopFileActions::run(u, true);
134 else if (isExecutableFile(u, _mimetype)) {
135 if (u.isLocalFile() && runExecutables) {
136 if (KAuthorized::authorize("shell_access")) {
137 return (KRun::runCommand(KShell::quoteArg(u.toLocalFile()), QString(), QString(), window, asn)); // just execute the url as a command
138 // ## TODO implement deleting the file if tempFile==true
140 else {
141 noAuth = true;
144 else if (_mimetype == QLatin1String("application/x-executable")) {
145 noRun = true;
148 else if (isExecutable(_mimetype)) {
149 if (!runExecutables) {
150 noRun = true;
153 if (!KAuthorized::authorize("shell_access")) {
154 noAuth = true;
158 if (noRun) {
159 KMessageBox::sorry(window,
160 i18n("<qt>The file <b>%1</b> is an executable program. "
161 "For safety it will not be started.</qt>", Qt::escape(u.prettyUrl())));
162 return false;
164 if (noAuth) {
165 KMessageBoxWrapper::error(window,
166 i18n("<qt>You do not have permission to run <b>%1</b>.</qt>", Qt::escape(u.prettyUrl())));
167 return false;
170 KUrl::List lst;
171 lst.append(u);
173 KService::Ptr offer = KMimeTypeTrader::self()->preferredService(_mimetype);
175 if (!offer) {
176 // Open-with dialog
177 // TODO : pass the mimetype as a parameter, to show it (comment field) in the dialog !
178 // Hmm, in fact KOpenWithDialog::setServiceType already guesses the mimetype from the first URL of the list...
179 return displayOpenWithDialog(lst, window, tempFile, suggestedFileName, asn);
182 return KRun::run(*offer, lst, window, tempFile, suggestedFileName, asn);
185 bool KRun::displayOpenWithDialog(const KUrl::List& lst, QWidget* window, bool tempFiles,
186 const QString& suggestedFileName, const QByteArray& asn)
188 if (!KAuthorized::authorizeKAction("openwith")) {
189 KMessageBox::sorry(window,
190 i18n("You are not authorized to select an application to open this file."));
191 return false;
194 #ifdef Q_WS_WIN
195 KConfigGroup cfgGroup(KGlobal::config(), "KOpenWithDialog Settings");
196 if (cfgGroup.readEntry("Native", true)) {
197 return KRun::KRunPrivate::displayNativeOpenWithDialog(lst, window, tempFiles,
198 suggestedFileName, asn);
200 #endif
201 KOpenWithDialog l(lst, i18n("Open with:"), QString(), window);
202 if (l.exec()) {
203 KService::Ptr service = l.service();
204 if (service) {
205 return KRun::run(*service, lst, window, tempFiles, suggestedFileName, asn);
208 kDebug(7010) << "No service set, running " << l.text();
209 return KRun::run(l.text(), lst, window, false, suggestedFileName, asn); // TODO handle tempFiles
211 return false;
214 void KRun::shellQuote(QString &_str)
216 // Credits to Walter, says Bernd G. :)
217 if (_str.isEmpty()) { // Don't create an explicit empty parameter
218 return;
220 QChar q('\'');
221 _str.replace(q, "'\\''").prepend(q).append(q);
225 class KRunMX1 : public KMacroExpanderBase
227 public:
228 KRunMX1(const KService &_service) :
229 KMacroExpanderBase('%'), hasUrls(false), hasSpec(false), service(_service) {}
231 bool hasUrls: 1, hasSpec: 1;
233 protected:
234 virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
236 private:
237 const KService &service;
241 KRunMX1::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
243 uint option = str[pos + 1].unicode();
244 switch (option) {
245 case 'c':
246 ret << service.name().replace('%', "%%");
247 break;
248 case 'k':
249 ret << service.entryPath().replace('%', "%%");
250 break;
251 case 'i':
252 ret << "-icon" << service.icon().replace('%', "%%");
253 break;
254 case 'm':
255 // ret << "-miniicon" << service.icon().replace( '%', "%%" );
256 kWarning() << "-miniicon isn't supported anymore (service"
257 << service.name() << ')';
258 break;
259 case 'u':
260 case 'U':
261 hasUrls = true;
262 /* fallthrough */
263 case 'f':
264 case 'F':
265 case 'n':
266 case 'N':
267 case 'd':
268 case 'D':
269 case 'v':
270 hasSpec = true;
271 /* fallthrough */
272 default:
273 return -2; // subst with same and skip
275 return 2;
278 class KRunMX2 : public KMacroExpanderBase
280 public:
281 KRunMX2(const KUrl::List &_urls) :
282 KMacroExpanderBase('%'), ignFile(false), urls(_urls) {}
284 bool ignFile: 1;
286 protected:
287 virtual int expandEscapedMacro(const QString &str, int pos, QStringList &ret);
289 private:
290 void subst(int option, const KUrl &url, QStringList &ret);
292 const KUrl::List &urls;
295 void
296 KRunMX2::subst(int option, const KUrl &url, QStringList &ret)
298 switch (option) {
299 case 'u':
300 ret << ((url.isLocalFile() && url.fragment().isNull() && url.encodedQuery().isNull()) ?
301 url.toLocalFile() : url.url());
302 break;
303 case 'd':
304 ret << url.directory();
305 break;
306 case 'f':
307 ret << url.path();
308 break;
309 case 'n':
310 ret << url.fileName();
311 break;
312 case 'v':
313 if (url.isLocalFile() && QFile::exists(url.toLocalFile())) {
314 ret << KDesktopFile(url.path()).desktopGroup().readEntry("Dev");
316 break;
318 return;
322 KRunMX2::expandEscapedMacro(const QString &str, int pos, QStringList &ret)
324 uint option = str[pos + 1].unicode();
325 switch (option) {
326 case 'f':
327 case 'u':
328 case 'n':
329 case 'd':
330 case 'v':
331 if (urls.isEmpty()) {
332 if (!ignFile) {
333 kDebug() << "No URLs supplied to single-URL service" << str;
336 else if (urls.count() > 1) {
337 kWarning() << urls.count() << "URLs supplied to single-URL service" << str;
339 else {
340 subst(option, urls.first(), ret);
342 break;
343 case 'F':
344 case 'U':
345 case 'N':
346 case 'D':
347 option += 'a' - 'A';
348 for (KUrl::List::ConstIterator it = urls.begin(); it != urls.end(); ++it)
349 subst(option, *it, ret);
350 break;
351 case '%':
352 ret = QStringList(QLatin1String("%"));
353 break;
354 default:
355 return -2; // subst with same and skip
357 return 2;
360 QStringList KRun::processDesktopExec(const KService &_service, const KUrl::List& _urls, bool tempFiles, const QString& suggestedFileName)
362 QString exec = _service.exec();
363 if (exec.isEmpty()) {
364 kWarning() << "KRun: no Exec field in `" << _service.entryPath() << "' !";
365 return QStringList();
368 QStringList result;
369 bool appHasTempFileOption;
371 KRunMX1 mx1(_service);
372 KRunMX2 mx2(_urls);
374 if (!mx1.expandMacrosShellQuote(exec)) { // Error in shell syntax
375 kWarning() << "KRun: syntax error in command" << _service.exec() << ", service" << _service.name();
376 return QStringList();
379 // FIXME: the current way of invoking kioexec disables term and su use
381 // Check if we need "tempexec" (kioexec in fact)
382 appHasTempFileOption = tempFiles && _service.property("X-KDE-HasTempFileOption").toBool();
383 if (tempFiles && !appHasTempFileOption && _urls.size()) {
384 const QString kioexec = KStandardDirs::findExe("kioexec");
385 Q_ASSERT(!kioexec.isEmpty());
386 result << kioexec << "--tempfiles" << exec;
387 if (!suggestedFileName.isEmpty()) {
388 result << "--suggestedfilename";
389 result << suggestedFileName;
391 result += _urls.toStringList();
392 return result;
395 // Check if we need kioexec
396 if (!mx1.hasUrls) {
397 for (KUrl::List::ConstIterator it = _urls.begin(); it != _urls.end(); ++it)
398 if (!(*it).isLocalFile() && !KProtocolInfo::isHelperProtocol(*it)) {
399 // We need to run the app through kioexec
400 const QString kioexec = KStandardDirs::findExe("kioexec");
401 Q_ASSERT(!kioexec.isEmpty());
402 result << kioexec;
403 if (tempFiles) {
404 result << "--tempfiles";
406 if (!suggestedFileName.isEmpty()) {
407 result << "--suggestedfilename";
408 result << suggestedFileName;
410 result << exec;
411 result += _urls.toStringList();
412 return result;
416 if (appHasTempFileOption) {
417 exec += " --tempfile";
420 // Did the user forget to append something like '%f'?
421 // If so, then assume that '%f' is the right choice => the application
422 // accepts only local files.
423 if (!mx1.hasSpec) {
424 exec += " %f";
425 mx2.ignFile = true;
428 mx2.expandMacrosShellQuote(exec); // syntax was already checked, so don't check return value
431 1 = need_shell, 2 = terminal, 4 = su
433 0 << split(cmd)
434 1 << "sh" << "-c" << cmd
435 2 << split(term) << "-e" << split(cmd)
436 3 << split(term) << "-e" << "sh" << "-c" << cmd
438 4 << "kdesu" << "-u" << user << "-c" << cmd
439 5 << "kdesu" << "-u" << user << "-c" << ("sh -c " + quote(cmd))
440 6 << split(term) << "-e" << "su" << user << "-c" << cmd
441 7 << split(term) << "-e" << "su" << user << "-c" << ("sh -c " + quote(cmd))
443 "sh -c" is needed in the "su" case, too, as su uses the user's login shell, not sh.
444 this could be optimized with the -s switch of some su versions (e.g., debian linux).
447 if (_service.terminal()) {
448 KConfigGroup cg(KGlobal::config(), "General");
449 QString terminal = cg.readPathEntry("TerminalApplication", "konsole");
450 if (terminal == "konsole") {
451 if (!_service.path().isEmpty()) {
452 terminal += " --workdir " + KShell::quoteArg(_service.path());
454 terminal += " -caption=%c %i %m";
456 terminal += ' ';
457 terminal += _service.terminalOptions();
458 if (!mx1.expandMacrosShellQuote(terminal)) {
459 kWarning() << "KRun: syntax error in command" << terminal << ", service" << _service.name();
460 return QStringList();
462 mx2.expandMacrosShellQuote(terminal);
463 result = KShell::splitArgs(terminal); // assuming that the term spec never needs a shell!
464 result << "-e";
467 KShell::Errors err;
468 QStringList execlist = KShell::splitArgs(exec, KShell::AbortOnMeta | KShell::TildeExpand, &err);
469 if (err == KShell::NoError && !execlist.isEmpty()) { // mx1 checked for syntax errors already
470 // Resolve the executable to ensure that helpers in lib/kde4/libexec/ are found.
471 // Too bad for commands that need a shell - they must reside in $PATH.
472 const QString exePath = KStandardDirs::findExe(execlist[0]);
473 if (!exePath.isEmpty()) {
474 execlist[0] = exePath;
477 if (_service.substituteUid()) {
478 if (_service.terminal()) {
479 result << "su";
481 else {
482 result << KStandardDirs::findExe("kdesu") << "-u";
485 result << _service.username() << "-c";
486 if (err == KShell::FoundMeta) {
487 exec = "/bin/sh -c " + KShell::quoteArg(exec);
489 else {
490 exec = KShell::joinArgs(execlist);
492 result << exec;
494 else {
495 if (err == KShell::FoundMeta) {
496 result << "/bin/sh" << "-c" << exec;
498 else {
499 result += execlist;
503 return result;
506 //static
507 QString KRun::binaryName(const QString & execLine, bool removePath)
509 // Remove parameters and/or trailing spaces.
510 const QStringList args = KShell::splitArgs(execLine);
511 for (QStringList::ConstIterator it = args.begin(); it != args.end(); ++it)
512 if (!(*it).contains('=')) {
513 // Remove path if wanted
514 return removePath ? (*it).mid((*it).lastIndexOf('/') + 1) : *it;
516 return QString();
519 static bool runCommandInternal(KProcess* proc, const KService* service, const QString& executable,
520 const QString &userVisibleName, const QString & iconName, QWidget* window,
521 const QByteArray& asn)
523 if (window != NULL) {
524 window = window->topLevelWidget();
526 if (service && !service->entryPath().isEmpty()
527 && !KDesktopFile::isAuthorizedDesktopFile(service->entryPath()))
529 kWarning() << "No authorization to execute " << service->entryPath();
530 KMessageBox::sorry(window, i18n("You are not authorized to execute this file."));
531 delete proc;
532 return false;
535 QString bin = KRun::binaryName(executable, true);
536 #ifdef Q_WS_X11 // Startup notification doesn't work with QT/E, service isn't needed without Startup notification
537 bool silent;
538 QByteArray wmclass;
539 KStartupInfoId id;
540 bool startup_notify = (asn != "0" && KRun::checkStartupNotify(QString() /*unused*/, service, &silent, &wmclass));
541 if (startup_notify) {
542 id.initId(asn);
543 id.setupStartupEnv();
544 KStartupInfoData data;
545 data.setHostname();
546 data.setBin(bin);
547 if (!userVisibleName.isEmpty()) {
548 data.setName(userVisibleName);
550 else if (service && !service->name().isEmpty()) {
551 data.setName(service->name());
553 data.setDescription(i18n("Launching %1" , data.name()));
554 if (!iconName.isEmpty()) {
555 data.setIcon(iconName);
557 else if (service && !service->icon().isEmpty()) {
558 data.setIcon(service->icon());
560 if (!wmclass.isEmpty()) {
561 data.setWMClass(wmclass);
563 if (silent) {
564 data.setSilent(KStartupInfoData::Yes);
566 data.setDesktop(KWindowSystem::currentDesktop());
567 if (window) {
568 data.setLaunchedBy(window->winId());
570 KStartupInfo::sendStartup(id, data);
572 int pid = KProcessRunner::run(proc, executable, id);
573 if (startup_notify && pid) {
574 KStartupInfoData data;
575 data.addPid(pid);
576 KStartupInfo::sendChange(id, data);
577 KStartupInfo::resetStartupEnv();
579 return pid != 0;
580 #else
581 Q_UNUSED(userVisibleName);
582 Q_UNUSED(iconName);
583 return KProcessRunner::run(proc, bin) != 0;
584 #endif
587 // This code is also used in klauncher.
588 bool KRun::checkStartupNotify(const QString& /*binName*/, const KService* service, bool* silent_arg, QByteArray* wmclass_arg)
590 bool silent = false;
591 QByteArray wmclass;
592 if (service && service->property("StartupNotify").isValid()) {
593 silent = !service->property("StartupNotify").toBool();
594 wmclass = service->property("StartupWMClass").toString().toLatin1();
596 else if (service && service->property("X-KDE-StartupNotify").isValid()) {
597 silent = !service->property("X-KDE-StartupNotify").toBool();
598 wmclass = service->property("X-KDE-WMClass").toString().toLatin1();
600 else { // non-compliant app
601 if (service) {
602 if (service->isApplication()) {
603 wmclass = "0"; // doesn't have .desktop entries needed, start as non-compliant
605 else {
606 return false; // no startup notification at all
609 else {
610 #if 0
611 // Create startup notification even for apps for which there shouldn't be any,
612 // just without any visual feedback. This will ensure they'll be positioned on the proper
613 // virtual desktop, and will get user timestamp from the ASN ID.
614 wmclass = "0";
615 silent = true;
616 #else // That unfortunately doesn't work, when the launched non-compliant application
617 // launches another one that is compliant and there is any delay inbetween (bnc:#343359)
618 return false;
619 #endif
622 if (silent_arg != NULL) {
623 *silent_arg = silent;
625 if (wmclass_arg != NULL) {
626 *wmclass_arg = wmclass;
628 return true;
631 static bool runTempService(const KService& _service, const KUrl::List& _urls, QWidget* window,
632 bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
634 if (!_urls.isEmpty()) {
635 kDebug(7010) << "runTempService: first url " << _urls.first().url();
638 QStringList args;
639 if ((_urls.count() > 1) && !_service.allowMultipleFiles()) {
640 // We need to launch the application N times. That sucks.
641 // We ignore the result for application 2 to N.
642 // For the first file we launch the application in the
643 // usual way. The reported result is based on this
644 // application.
645 KUrl::List::ConstIterator it = _urls.begin();
646 while (++it != _urls.end()) {
647 KUrl::List singleUrl;
648 singleUrl.append(*it);
649 runTempService(_service, singleUrl, window, tempFiles, suggestedFileName, QByteArray());
651 KUrl::List singleUrl;
652 singleUrl.append(_urls.first());
653 args = KRun::processDesktopExec(_service, singleUrl, tempFiles, suggestedFileName);
655 else {
656 args = KRun::processDesktopExec(_service, _urls, tempFiles, suggestedFileName);
658 if (args.isEmpty()) {
659 KMessageBox::sorry(window, i18n("Error processing Exec field in %1", _service.entryPath()));
660 return false;
662 kDebug(7010) << "runTempService: KProcess args=" << args;
664 KProcess * proc = new KProcess;
665 *proc << args;
667 if (!_service.path().isEmpty()) {
668 proc->setWorkingDirectory(_service.path());
671 return runCommandInternal(proc, &_service, KRun::binaryName(_service.exec(), false),
672 _service.name(), _service.icon(), window, asn);
675 // WARNING: don't call this from processDesktopExec, since klauncher uses that too...
676 static KUrl::List resolveURLs(const KUrl::List& _urls, const KService& _service)
678 // Check which protocols the application supports.
679 // This can be a list of actual protocol names, or just KIO for KDE apps.
680 QStringList supportedProtocols = _service.property("X-KDE-Protocols").toStringList();
681 KRunMX1 mx1(_service);
682 QString exec = _service.exec();
683 if (mx1.expandMacrosShellQuote(exec) && !mx1.hasUrls) {
684 Q_ASSERT(supportedProtocols.isEmpty()); // huh? If you support protocols you need %u or %U...
686 else {
687 if (supportedProtocols.isEmpty()) {
688 // compat mode: assume KIO if not set and it's a KDE app
689 QStringList categories = _service.property("Categories").toStringList();
690 if (categories.contains("KDE")) {
691 supportedProtocols.append("KIO");
693 else { // if no KDE app, be a bit over-generic
694 supportedProtocols.append("http");
695 supportedProtocols.append("ftp");
699 kDebug(7010) << "supportedProtocols:" << supportedProtocols;
701 KUrl::List urls(_urls);
702 if (!supportedProtocols.contains("KIO")) {
703 for (KUrl::List::Iterator it = urls.begin(); it != urls.end(); ++it) {
704 const KUrl url = *it;
705 bool supported = url.isLocalFile() || supportedProtocols.contains(url.protocol().toLower());
706 kDebug(7010) << "Looking at url=" << url << " supported=" << supported;
707 if (!supported && KProtocolInfo::protocolClass(url.protocol()) == ":local") {
708 // Maybe we can resolve to a local URL?
709 KUrl localURL = KIO::NetAccess::mostLocalUrl(url, 0);
710 if (localURL != url) {
711 *it = localURL;
712 kDebug(7010) << "Changed to " << localURL;
717 return urls;
720 // Simple KDialog that resizes the given text edit after being shown to more
721 // or less fit the enclosed text.
722 class SecureMessageDialog : public KDialog
724 public:
725 SecureMessageDialog(QWidget *parent) : KDialog(parent), m_textEdit(0)
729 void setTextEdit(QPlainTextEdit *textEdit)
731 m_textEdit = textEdit;
734 protected:
735 virtual void showEvent(QShowEvent* e)
737 // Now that we're shown, use our width to calculate a good
738 // bounding box for the text, and resize m_textEdit appropriately.
739 KDialog::showEvent(e);
741 if(!m_textEdit)
742 return;
744 QSize fudge(20, 24); // About what it sounds like :-/
746 // Form rect with a lot of height for bounding. Use no more than
747 // 5 lines.
748 QRect curRect(m_textEdit->rect());
749 QFontMetrics metrics(fontMetrics());
750 curRect.setHeight(5 * metrics.lineSpacing());
751 curRect.setWidth(qMax(curRect.width(), 300)); // At least 300 pixels ok?
753 QString text(m_textEdit->toPlainText());
754 curRect = metrics.boundingRect(curRect, Qt::TextWordWrap | Qt::TextSingleLine, text);
756 // Scroll bars interfere. If we don't think there's enough room, enable
757 // the vertical scrollbar however.
758 m_textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
759 if(curRect.height() < m_textEdit->height()) { // then we've got room
760 m_textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
761 m_textEdit->setMaximumHeight(curRect.height() + fudge.height());
764 m_textEdit->setMinimumSize(curRect.size() + fudge);
765 m_textEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
766 updateGeometry();
769 private:
770 QPlainTextEdit *m_textEdit;
773 // Helper function to make the given .desktop file executable by ensuring
774 // that a #!/usr/bin/env xdg-open line is added if necessary and the file has
775 // the +x bit set for the user. Returns false if either fails.
776 static bool makeFileExecutable(const QString &fileName)
778 // Open the file and read the first two characters, check if it's
779 // #!. If not, create a new file, prepend appropriate lines, and copy
780 // over.
781 QFile desktopFile(fileName);
782 if (!desktopFile.open(QFile::ReadOnly)) {
783 kError(7010) << "Error opening service" << fileName << desktopFile.errorString();
784 return false;
787 QByteArray header = desktopFile.peek(2); // First two chars of file
788 if (header.size() == 0) {
789 kError(7010) << "Error inspecting service" << fileName << desktopFile.errorString();
790 return false; // Some kind of error
793 if (header != "#!") {
794 // Add header
795 KSaveFile saveFile;
796 saveFile.setFileName(fileName);
797 if (!saveFile.open()) {
798 kError(7010) << "Unable to open replacement file for" << fileName << saveFile.errorString();
799 return false;
802 QByteArray shebang("#!/usr/bin/env xdg-open\n");
803 if (saveFile.write(shebang) != shebang.size()) {
804 kError(7010) << "Error occurred adding header for" << fileName << saveFile.errorString();
805 saveFile.abort();
806 return false;
809 // Now copy the one into the other and then close and reopen desktopFile
810 QByteArray desktopData(desktopFile.readAll());
811 if (desktopData.isEmpty()) {
812 kError(7010) << "Unable to read service" << fileName << desktopFile.errorString();
813 saveFile.abort();
814 return false;
817 if (saveFile.write(desktopData) != desktopData.size()) {
818 kError(7010) << "Error copying service" << fileName << saveFile.errorString();
819 saveFile.abort();
820 return false;
823 desktopFile.close();
824 if (!saveFile.finalize()) { // Figures....
825 kError(7010) << "Error committing changes to service" << fileName << saveFile.errorString();
826 return false;
829 if (!desktopFile.open(QFile::ReadOnly)) {
830 kError(7010) << "Error re-opening service" << fileName << desktopFile.errorString();
831 return false;
833 } // Add header
835 // corresponds to owner on unix, which will have to do since if the user
836 // isn't the owner we can't change perms anyways.
837 if (!desktopFile.setPermissions(QFile::ExeUser | desktopFile.permissions())) {
838 kError(7010) << "Unable to change permissions for" << fileName << desktopFile.errorString();
839 return false;
842 // whew
843 return true;
846 // Helper function to make a .desktop file executable if prompted by the user.
847 // returns true if KRun::run() should continue with execution, false if user declined
848 // to make the file executable or we failed to make it executable.
849 static bool makeServiceExecutable(const KService& service, QWidget* window)
851 if (!KAuthorized::authorize("run_desktop_files")) {
852 kWarning() << "No authorization to execute " << service.entryPath();
853 KMessageBox::sorry(window, i18n("You are not authorized to execute this service."));
854 return false; // Don't circumvent the Kiosk
857 KGuiItem continueItem = KStandardGuiItem::cont();
859 SecureMessageDialog *baseDialog = new SecureMessageDialog(window);
861 baseDialog->setButtons(KDialog::Ok | KDialog::Cancel);
862 baseDialog->setButtonGuiItem(KDialog::Ok, continueItem);
863 baseDialog->setDefaultButton(KDialog::Cancel);
864 baseDialog->setButtonFocus(KDialog::Cancel);
865 baseDialog->setCaption(i18nc("Warning about executing unknown .desktop file", "Warning"));
867 // Dialog will have explanatory text with a disabled lineedit with the
868 // Exec= to make it visually distinct.
869 QWidget *baseWidget = new QWidget(baseDialog);
870 QHBoxLayout *mainLayout = new QHBoxLayout(baseWidget);
872 QLabel *iconLabel = new QLabel(baseWidget);
873 QPixmap warningIcon(KIconLoader::global()->loadIcon("dialog-warning", KIconLoader::NoGroup, KIconLoader::SizeHuge));
874 mainLayout->addWidget(iconLabel);
875 iconLabel->setPixmap(warningIcon);
877 QVBoxLayout *contentLayout = new QVBoxLayout;
878 QString warningMessage = i18nc("program name follows in a line edit below",
879 "This will start the program:");
881 QLabel *message = new QLabel(warningMessage, baseWidget);
882 contentLayout->addWidget(message);
884 // We can use KStandardDirs::findExe to resolve relative pathnames
885 // but that gets rid of the command line arguments.
886 QString program = KStandardDirs::realFilePath(service.exec());
888 QPlainTextEdit *textEdit = new QPlainTextEdit(baseWidget);
889 textEdit->setPlainText(program);
890 textEdit->setReadOnly(true);
891 contentLayout->addWidget(textEdit);
893 QLabel *footerLabel = new QLabel(i18n("If you do not trust this program, click Cancel"));
894 contentLayout->addWidget(footerLabel);
895 contentLayout->addStretch(0); // Don't allow the text edit to expand
897 mainLayout->addLayout(contentLayout);
899 baseDialog->setMainWidget(baseWidget);
900 baseDialog->setTextEdit(textEdit);
902 // Constrain maximum size. Minimum size set in
903 // the dialog's show event.
904 QSize screenSize = QApplication::desktop()->screen()->size();
905 baseDialog->resize(screenSize.width() / 4, 50);
906 baseDialog->setMaximumHeight(screenSize.height() / 3);
907 baseDialog->setMaximumWidth(screenSize.width() / 10 * 8);
909 int result = baseDialog->exec();
910 if (result != KDialog::Accepted) {
911 return false;
914 // Assume that service is an absolute path since we're being called (relative paths
915 // would have been allowed unless Kiosk said no, therefore we already know where the
916 // .desktop file is. Now add a header to it if it doesn't already have one
917 // and add the +x bit.
919 if (!::makeFileExecutable(service.entryPath())) {
920 QString serviceName = service.name();
921 if(serviceName.isEmpty())
922 serviceName = service.genericName();
924 KMessageBox::sorry(
925 window,
926 i18n("Unable to make the service %1 executable, aborting execution", serviceName)
929 return false;
932 return true;
935 bool KRun::run(const KService& _service, const KUrl::List& _urls, QWidget* window,
936 bool tempFiles, const QString& suggestedFileName, const QByteArray& asn)
938 if (!_service.entryPath().isEmpty() &&
939 !KDesktopFile::isAuthorizedDesktopFile(_service.entryPath()) &&
940 !::makeServiceExecutable(_service, window))
942 return false;
945 if (!tempFiles) {
946 // Remember we opened those urls, for the "recent documents" menu in kicker
947 KUrl::List::ConstIterator it = _urls.begin();
948 for (; it != _urls.end(); ++it) {
949 //kDebug(7010) << "KRecentDocument::adding " << (*it).url();
950 KRecentDocument::add(*it, _service.desktopEntryName());
954 if (tempFiles || _service.entryPath().isEmpty() || !suggestedFileName.isEmpty()) {
955 return runTempService(_service, _urls, window, tempFiles, suggestedFileName, asn);
958 kDebug(7010) << "KRun::run " << _service.entryPath();
960 if (!_urls.isEmpty()) {
961 kDebug(7010) << "First url " << _urls.first().url();
964 // Resolve urls if needed, depending on what the app supports
965 const KUrl::List urls = resolveURLs(_urls, _service);
967 QString error;
968 int pid = 0;
970 QByteArray myasn = asn;
971 // startServiceByDesktopPath() doesn't take QWidget*, add it to the startup info now
972 if (window != NULL) {
973 if (myasn.isEmpty()) {
974 myasn = KStartupInfo::createNewStartupId();
976 if (myasn != "0") {
977 KStartupInfoId id;
978 id.initId(myasn);
979 KStartupInfoData data;
980 data.setLaunchedBy(window->winId());
981 KStartupInfo::sendChange(id, data);
985 int i = KToolInvocation::startServiceByDesktopPath(
986 _service.entryPath(), urls.toStringList(), &error, 0L, &pid, myasn
989 if (i != 0) {
990 kDebug(7010) << error;
991 KMessageBox::sorry(window, error);
992 return false;
995 kDebug(7010) << "startServiceByDesktopPath worked fine";
996 return true;
1000 bool KRun::run(const QString& _exec, const KUrl::List& _urls, QWidget* window, const QString& _name,
1001 const QString& _icon, const QByteArray& asn)
1003 KService::Ptr service(new KService(_name, _exec, _icon));
1005 return run(*service, _urls, window, false, QString(), asn);
1008 bool KRun::runCommand(const QString &cmd, QWidget* window)
1010 const QString bin = KShell::splitArgs(cmd).first();
1011 return KRun::runCommand(cmd, bin, bin /*iconName*/, window, QByteArray());
1014 bool KRun::runCommand(const QString& cmd, const QString &execName, const QString & iconName, QWidget* window, const QByteArray& asn)
1016 kDebug(7010) << "runCommand " << cmd << "," << execName;
1017 KProcess * proc = new KProcess;
1018 proc->setShellCommand(cmd);
1019 QString bin = binaryName(execName, true);
1020 KService::Ptr service = KService::serviceByDesktopName(bin);
1021 return runCommandInternal(proc, service.data(),
1022 execName /*executable to check for in slotProcessExited*/,
1023 execName /*user-visible name*/,
1024 iconName, window, asn);
1027 KRun::KRun(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
1028 bool showProgressInfo, const QByteArray& asn)
1029 : d(new KRunPrivate(this))
1031 d->m_timer.setObjectName("KRun::timer");
1032 d->m_timer.setSingleShot(true);
1033 d->init(url, window, mode, isLocalFile, showProgressInfo, asn);
1036 void KRun::KRunPrivate::init(const KUrl& url, QWidget* window, mode_t mode, bool isLocalFile,
1037 bool showProgressInfo, const QByteArray& asn)
1039 m_bFault = false;
1040 m_bAutoDelete = true;
1041 m_bProgressInfo = showProgressInfo;
1042 m_bFinished = false;
1043 m_job = 0L;
1044 m_strURL = url;
1045 m_bScanFile = false;
1046 m_bIsDirectory = false;
1047 m_bIsLocalFile = isLocalFile;
1048 m_mode = mode;
1049 m_runExecutables = true;
1050 m_window = window;
1051 m_asn = asn;
1052 q->setEnableExternalBrowser(true);
1054 // Start the timer. This means we will return to the event
1055 // loop and do initialization afterwards.
1056 // Reason: We must complete the constructor before we do anything else.
1057 m_bInit = true;
1058 q->connect(&m_timer, SIGNAL(timeout()), q, SLOT(slotTimeout()));
1059 startTimer();
1060 //kDebug(7010) << "new KRun" << q << url << "timer=" << &m_timer;
1062 KGlobal::ref();
1065 void KRun::init()
1067 kDebug(7010) << "INIT called";
1068 if (!d->m_strURL.isValid()) {
1069 // TODO KDE5: call virtual method on error (see BrowserRun::init)
1070 d->m_showingDialog = true;
1071 KMessageBoxWrapper::error(d->m_window, i18n("Malformed URL\n%1", d->m_strURL.url()));
1072 d->m_showingDialog = false;
1073 d->m_bFault = true;
1074 d->m_bFinished = true;
1075 d->startTimer();
1076 return;
1078 if (!KAuthorized::authorizeUrlAction("open", KUrl(), d->m_strURL)) {
1079 QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->m_strURL.prettyUrl());
1080 d->m_showingDialog = true;
1081 KMessageBoxWrapper::error(d->m_window, msg);
1082 d->m_showingDialog = false;
1083 d->m_bFault = true;
1084 d->m_bFinished = true;
1085 d->startTimer();
1086 return;
1089 if (!d->m_bIsLocalFile && d->m_strURL.isLocalFile()) {
1090 d->m_bIsLocalFile = true;
1093 QString exec;
1094 if (d->m_strURL.protocol().startsWith("http")) {
1095 exec = d->m_externalBrowser;
1098 if (d->m_bIsLocalFile) {
1099 if (d->m_mode == 0) {
1100 KDE_struct_stat buff;
1101 if (KDE::stat(d->m_strURL.path(), &buff) == -1) {
1102 d->m_showingDialog = true;
1103 KMessageBoxWrapper::error(d->m_window,
1104 i18n("<qt>Unable to run the command specified. "
1105 "The file or folder <b>%1</b> does not exist.</qt>" ,
1106 Qt::escape(d->m_strURL.prettyUrl())));
1107 d->m_showingDialog = false;
1108 d->m_bFault = true;
1109 d->m_bFinished = true;
1110 d->startTimer();
1111 return;
1113 d->m_mode = buff.st_mode;
1116 KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL, d->m_mode, d->m_bIsLocalFile);
1117 assert(mime);
1118 kDebug(7010) << "MIME TYPE is " << mime->name();
1119 mimeTypeDetermined(mime->name());
1120 return;
1122 else if (!exec.isEmpty() || KProtocolInfo::isHelperProtocol(d->m_strURL)) {
1123 kDebug(7010) << "Helper protocol";
1125 bool ok = false;
1126 KUrl::List urls;
1127 urls.append(d->m_strURL);
1128 if (exec.isEmpty()) {
1129 exec = KProtocolInfo::exec(d->m_strURL.protocol());
1130 if (exec.isEmpty()) {
1131 mimeTypeDetermined(KProtocolManager::defaultMimetype(d->m_strURL));
1132 return;
1134 run(exec, urls, d->m_window, false, QString(), d->m_asn);
1135 ok = true;
1137 else if (exec.startsWith('!')) {
1138 exec = exec.mid(1); // Literal command
1139 exec += " %u";
1140 run(exec, urls, d->m_window, false, QString(), d->m_asn);
1141 ok = true;
1143 else {
1144 KService::Ptr service = KService::serviceByStorageId(exec);
1145 if (service) {
1146 run(*service, urls, d->m_window, false, QString(), d->m_asn);
1147 ok = true;
1151 if (ok) {
1152 d->m_bFinished = true;
1153 // will emit the error and autodelete this
1154 d->startTimer();
1155 return;
1159 // Did we already get the information that it is a directory ?
1160 if (S_ISDIR(d->m_mode)) {
1161 mimeTypeDetermined("inode/directory");
1162 return;
1165 // Let's see whether it is a directory
1167 if (!KProtocolManager::supportsListing(d->m_strURL)) {
1168 //kDebug(7010) << "Protocol has no support for listing";
1169 // No support for listing => it can't be a directory (example: http)
1170 scanFile();
1171 return;
1174 kDebug(7010) << "Testing directory (stating)";
1176 // It may be a directory or a file, let's stat
1177 KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
1178 KIO::StatJob *job = KIO::stat(d->m_strURL, KIO::StatJob::SourceSide, 0 /* no details */, flags);
1179 job->ui()->setWindow(d->m_window);
1180 connect(job, SIGNAL(result(KJob *)),
1181 this, SLOT(slotStatResult(KJob *)));
1182 d->m_job = job;
1183 kDebug(7010) << " Job " << job << " is about stating " << d->m_strURL.url();
1186 KRun::~KRun()
1188 //kDebug(7010) << this;
1189 d->m_timer.stop();
1190 killJob();
1191 KGlobal::deref();
1192 //kDebug(7010) << this << "done";
1193 delete d;
1196 void KRun::scanFile()
1198 kDebug(7010) << d->m_strURL;
1199 // First, let's check for well-known extensions
1200 // Not when there is a query in the URL, in any case.
1201 if (d->m_strURL.query().isEmpty()) {
1202 KMimeType::Ptr mime = KMimeType::findByUrl(d->m_strURL);
1203 assert(mime);
1204 if (mime->name() != "application/octet-stream" || d->m_bIsLocalFile) {
1205 kDebug(7010) << "Scanfile: MIME TYPE is " << mime->name();
1206 mimeTypeDetermined(mime->name());
1207 return;
1211 // No mimetype found, and the URL is not local (or fast mode not allowed).
1212 // We need to apply the 'KIO' method, i.e. either asking the server or
1213 // getting some data out of the file, to know what mimetype it is.
1215 if (!KProtocolManager::supportsReading(d->m_strURL)) {
1216 kError(7010) << "#### NO SUPPORT FOR READING!";
1217 d->m_bFault = true;
1218 d->m_bFinished = true;
1219 d->startTimer();
1220 return;
1222 kDebug(7010) << this << " Scanning file " << d->m_strURL.url();
1224 KIO::JobFlags flags = d->m_bProgressInfo ? KIO::DefaultFlags : KIO::HideProgressInfo;
1225 KIO::TransferJob *job = KIO::get(d->m_strURL, KIO::NoReload /*reload*/, flags);
1226 job->ui()->setWindow(d->m_window);
1227 connect(job, SIGNAL(result(KJob *)),
1228 this, SLOT(slotScanFinished(KJob *)));
1229 connect(job, SIGNAL(mimetype(KIO::Job *, const QString &)),
1230 this, SLOT(slotScanMimeType(KIO::Job *, const QString &)));
1231 d->m_job = job;
1232 kDebug(7010) << " Job " << job << " is about getting from " << d->m_strURL.url();
1235 void KRun::slotTimeout()
1237 kDebug(7010) << this << " slotTimeout called";
1238 if (d->m_bInit) {
1239 d->m_bInit = false;
1240 init();
1241 return;
1244 if (d->m_bFault) {
1245 emit error();
1247 if (d->m_bFinished) {
1248 emit finished();
1250 else {
1251 if (d->m_bScanFile) {
1252 d->m_bScanFile = false;
1253 scanFile();
1254 return;
1256 else if (d->m_bIsDirectory) {
1257 d->m_bIsDirectory = false;
1258 mimeTypeDetermined("inode/directory");
1259 return;
1263 if (d->m_bAutoDelete) {
1264 deleteLater();
1265 return;
1269 void KRun::slotStatResult(KJob * job)
1271 d->m_job = 0L;
1272 if (job->error()) {
1273 d->m_showingDialog = true;
1274 kError(7010) << this << "ERROR" << job->error() << ' ' << job->errorString();
1275 job->uiDelegate()->showErrorMessage();
1276 //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
1277 d->m_showingDialog = false;
1279 d->m_bFault = true;
1280 d->m_bFinished = true;
1282 // will emit the error and autodelete this
1283 d->startTimer();
1286 else {
1288 kDebug(7010) << "Finished";
1289 if (!qobject_cast<KIO::StatJob*>(job)) {
1290 kFatal() << "job is a " << typeid(*job).name() << " should be a StatJob";
1293 const KIO::UDSEntry entry = ((KIO::StatJob*)job)->statResult();
1294 const mode_t mode = entry.numberValue(KIO::UDSEntry::UDS_FILE_TYPE);
1295 if (S_ISDIR(mode)) {
1296 d->m_bIsDirectory = true; // it's a dir
1298 else {
1299 d->m_bScanFile = true; // it's a file
1302 d->m_localPath = entry.stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
1304 // mimetype already known? (e.g. print:/manager)
1305 const QString knownMimeType = entry.stringValue(KIO::UDSEntry::UDS_MIME_TYPE) ;
1307 if (!knownMimeType.isEmpty()) {
1308 mimeTypeDetermined(knownMimeType);
1309 d->m_bFinished = true;
1312 // We should have found something
1313 assert(d->m_bScanFile || d->m_bIsDirectory);
1315 // Start the timer. Once we get the timer event this
1316 // protocol server is back in the pool and we can reuse it.
1317 // This gives better performance than starting a new slave
1318 d->startTimer();
1322 void KRun::slotScanMimeType(KIO::Job *, const QString &mimetype)
1324 if (mimetype.isEmpty()) {
1325 kWarning(7010) << "MimetypeJob didn't find a mimetype! Probably a kioslave bug.";
1327 mimeTypeDetermined(mimetype);
1328 d->m_job = 0;
1331 void KRun::slotScanFinished(KJob *job)
1333 d->m_job = 0;
1334 if (job->error()) {
1335 d->m_showingDialog = true;
1336 kError(7010) << this << "ERROR (stat):" << job->error() << ' ' << job->errorString();
1337 job->uiDelegate()->showErrorMessage();
1338 //kDebug(7010) << this << " KRun returning from showErrorDialog, starting timer to delete us";
1339 d->m_showingDialog = false;
1341 d->m_bFault = true;
1342 d->m_bFinished = true;
1344 // will emit the error and autodelete this
1345 d->startTimer();
1349 void KRun::mimeTypeDetermined(const QString& mimeType)
1351 // foundMimeType reimplementations might show a dialog box;
1352 // make sure some timer doesn't kill us meanwhile (#137678, #156447)
1353 Q_ASSERT(!d->m_showingDialog);
1354 d->m_showingDialog = true;
1356 foundMimeType(mimeType);
1358 d->m_showingDialog = false;
1361 void KRun::foundMimeType(const QString& type)
1363 kDebug(7010) << "Resulting mime type is " << type;
1366 // Automatically unzip stuff
1368 // Disabled since the new KIO doesn't have filters yet.
1370 if ( type == "application/x-gzip" ||
1371 type == "application/x-bzip" ||
1372 type == "application/x-bzip" )
1374 KUrl::List lst = KUrl::split( m_strURL );
1375 if ( lst.isEmpty() )
1377 QString tmp = i18n( "Malformed URL" );
1378 tmp += "\n";
1379 tmp += m_strURL.url();
1380 KMessageBoxWrapper::error( 0L, tmp );
1381 return;
1384 if ( type == "application/x-gzip" )
1385 lst.prepend( KUrl( "gzip:/decompress" ) );
1386 else if ( type == "application/x-bzip" )
1387 lst.prepend( KUrl( "bzip:/decompress" ) );
1388 else if ( type == "application/x-bzip" )
1389 lst.prepend( KUrl( "bzip2:/decompress" ) );
1390 else if ( type == "application/x-tar" )
1391 lst.prepend( KUrl( "tar:/" ) );
1393 // Move the HTML style reference to the leftmost URL
1394 KUrl::List::Iterator it = lst.begin();
1395 ++it;
1396 (*lst.begin()).setRef( (*it).ref() );
1397 (*it).setRef( QString() );
1399 // Create the new URL
1400 m_strURL = KUrl::join( lst );
1402 kDebug(7010) << "Now trying with " << debugString(m_strURL.url());
1404 killJob();
1406 // We don't know if this is a file or a directory. Let's test this first.
1407 // (For instance a tar.gz is a directory contained inside a file)
1408 // It may be a directory or a file, let's stat
1409 KIO::StatJob *job = KIO::stat( m_strURL, m_bProgressInfo );
1410 connect( job, SIGNAL( result( KJob * ) ),
1411 this, SLOT( slotStatResult( KJob * ) ) );
1412 m_job = job;
1414 return;
1417 KIO::TransferJob *job = qobject_cast<KIO::TransferJob *>(d->m_job);
1418 if (job) {
1419 job->putOnHold();
1420 KIO::Scheduler::publishSlaveOnHold();
1421 d->m_job = 0;
1424 Q_ASSERT(!d->m_bFinished);
1426 KMimeType::Ptr mime = KMimeType::mimeType(type, KMimeType::ResolveAliases);
1427 if (!mime) {
1428 kWarning(7010) << "Unknown mimetype " << type;
1431 // Support for preferred service setting, see setPreferredService
1432 if (!d->m_preferredService.isEmpty()) {
1433 kDebug(7010) << "Attempting to open with preferred service: " << d->m_preferredService;
1434 KService::Ptr serv = KService::serviceByDesktopName(d->m_preferredService);
1435 if (serv && serv->hasMimeType(mime.data())) {
1436 KUrl::List lst;
1437 lst.append(d->m_strURL);
1438 d->m_bFinished = KRun::run(*serv, lst, d->m_window, false, QString(), d->m_asn);
1439 /// Note: the line above means that if that service failed, we'll
1440 /// go to runUrl to maybe find another service, even though a dialog
1441 /// box was displayed. That's good if runUrl tries another service,
1442 /// but it's not good if it tries the same one :}
1446 // Resolve .desktop files from media:/, remote:/, applications:/ etc.
1447 if (mime && mime->is("application/x-desktop") && !d->m_localPath.isEmpty()) {
1448 d->m_strURL = KUrl();
1449 d->m_strURL.setPath(d->m_localPath);
1452 if (!d->m_bFinished && KRun::runUrl(d->m_strURL, type, d->m_window, false /*tempfile*/, d->m_runExecutables, d->m_suggestedFileName, d->m_asn)) {
1453 d->m_bFinished = true;
1455 else {
1456 d->m_bFinished = true;
1457 d->m_bFault = true;
1460 d->startTimer();
1463 void KRun::killJob()
1465 if (d->m_job) {
1466 kDebug(7010) << this << "m_job=" << d->m_job;
1467 d->m_job->kill();
1468 d->m_job = 0L;
1472 void KRun::abort()
1474 kDebug(7010) << this << "m_showingDialog=" << d->m_showingDialog;
1475 killJob();
1476 // If we're showing an error message box, the rest will be done
1477 // after closing the msgbox -> don't autodelete nor emit signals now.
1478 if (d->m_showingDialog) {
1479 return;
1481 d->m_bFault = true;
1482 d->m_bFinished = true;
1483 d->m_bInit = false;
1484 d->m_bScanFile = false;
1486 // will emit the error and autodelete this
1487 d->startTimer();
1490 bool KRun::hasError() const
1492 return d->m_bFault;
1495 bool KRun::hasFinished() const
1497 return d->m_bFinished;
1500 bool KRun::autoDelete() const
1502 return d->m_bAutoDelete;
1505 void KRun::setAutoDelete(bool b)
1507 d->m_bAutoDelete = b;
1510 void KRun::setEnableExternalBrowser(bool b)
1512 if (b) {
1513 d->m_externalBrowser = KConfigGroup(KGlobal::config(), "General").readEntry("BrowserApplication");
1515 else {
1516 d->m_externalBrowser.clear();
1520 void KRun::setPreferredService(const QString& desktopEntryName)
1522 d->m_preferredService = desktopEntryName;
1525 void KRun::setRunExecutables(bool b)
1527 d->m_runExecutables = b;
1530 void KRun::setSuggestedFileName(const QString& fileName)
1532 d->m_suggestedFileName = fileName;
1535 QString KRun::suggestedFileName() const
1537 return d->m_suggestedFileName;
1540 bool KRun::isExecutable(const QString& serviceType)
1542 return (serviceType == "application/x-desktop" ||
1543 serviceType == "application/x-executable" ||
1544 serviceType == "application/x-ms-dos-executable" ||
1545 serviceType == "application/x-shellscript");
1548 void KRun::setUrl(const KUrl &url)
1550 d->m_strURL = url;
1553 KUrl KRun::url() const
1555 return d->m_strURL;
1558 void KRun::setError(bool error)
1560 d->m_bFault = error;
1563 void KRun::setProgressInfo(bool progressInfo)
1565 d->m_bProgressInfo = progressInfo;
1568 bool KRun::progressInfo() const
1570 return d->m_bProgressInfo;
1573 void KRun::setFinished(bool finished)
1575 d->m_bFinished = finished;
1576 // TODO d->startTimer(); (and later on remove it from callers...)
1579 void KRun::setJob(KIO::Job *job)
1581 d->m_job = job;
1584 KIO::Job* KRun::job()
1586 return d->m_job;
1589 QTimer& KRun::timer()
1591 return d->m_timer;
1594 void KRun::setDoScanFile(bool scanFile)
1596 d->m_bScanFile = scanFile;
1599 bool KRun::doScanFile() const
1601 return d->m_bScanFile;
1604 void KRun::setIsDirecory(bool isDirectory)
1606 d->m_bIsDirectory = isDirectory;
1609 bool KRun::isDirectory() const
1611 return d->m_bIsDirectory;
1614 void KRun::setInitializeNextAction(bool initialize)
1616 d->m_bInit = initialize;
1619 bool KRun::initializeNextAction() const
1621 return d->m_bInit;
1624 void KRun::setIsLocalFile(bool isLocalFile)
1626 d->m_bIsLocalFile = isLocalFile;
1629 bool KRun::isLocalFile() const
1631 return d->m_bIsLocalFile;
1634 void KRun::setMode(mode_t mode)
1636 d->m_mode = mode;
1639 mode_t KRun::mode() const
1641 return d->m_mode;
1644 /****************/
1646 #ifndef Q_WS_X11
1647 int KProcessRunner::run(KProcess * p, const QString & executable)
1649 return (new KProcessRunner(p, executable))->pid();
1651 #else
1652 int KProcessRunner::run(KProcess * p, const QString & executable, const KStartupInfoId& id)
1654 return (new KProcessRunner(p, executable, id))->pid();
1656 #endif
1658 #ifndef Q_WS_X11
1659 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable)
1660 #else
1661 KProcessRunner::KProcessRunner(KProcess * p, const QString & executable, const KStartupInfoId& _id) :
1662 id(_id)
1663 #endif
1665 m_pid = 0;
1666 process = p;
1667 m_executable = executable;
1668 connect(process, SIGNAL(finished(int, QProcess::ExitStatus)),
1669 this, SLOT(slotProcessExited(int, QProcess::ExitStatus)));
1671 process->start();
1672 if (!process->waitForStarted()) {
1673 //kDebug() << "wait for started failed, exitCode=" << process->exitCode()
1674 // << "exitStatus=" << process->exitStatus();
1675 // Note that exitCode is 255 here (the first time), and 0 later on (bug?).
1676 slotProcessExited(255, process->exitStatus());
1678 else {
1679 #ifdef Q_WS_X11
1680 m_pid = process->pid();
1681 #endif
1685 KProcessRunner::~KProcessRunner()
1687 delete process;
1690 int KProcessRunner::pid() const
1692 return m_pid;
1695 void KProcessRunner::terminateStartupNotification()
1697 #ifdef Q_WS_X11
1698 if (!id.none()) {
1699 KStartupInfoData data;
1700 data.addPid(m_pid); // announce this pid for the startup notification has finished
1701 data.setHostname();
1702 KStartupInfo::sendFinish(id, data);
1704 #endif
1708 void
1709 KProcessRunner::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus)
1711 kDebug(7010) << m_executable << "exitCode=" << exitCode << "exitStatus=" << exitStatus;
1712 Q_UNUSED(exitStatus);
1714 terminateStartupNotification(); // do this before the messagebox
1715 if (exitCode != 0 && !m_executable.isEmpty()) {
1716 // Let's see if the error is because the exe doesn't exist.
1717 // When this happens, waitForStarted returns false, but not if kioexec
1718 // was involved, then we come here, that's why the code is here.
1720 // We'll try to find the executable relatively to current directory,
1721 // (or with a full path, if m_executable is absolute), and then in the PATH.
1722 if (!QFile(m_executable).exists() && KStandardDirs::findExe(m_executable).isEmpty()) {
1723 KGlobal::ref();
1724 KMessageBox::sorry(0L, i18n("Could not find the program '%1'", m_executable));
1725 KGlobal::deref();
1727 else {
1728 kDebug() << process->readAllStandardError();
1731 deleteLater();
1734 #include "krun.moc"
1735 #include "krun_p.moc"