fix tricky regression noticed by Vyacheslav Tokarev on Google Reader.
[kdelibs.git] / kinit / klauncher.cpp
blob69bcd5a25a58c80a2c1c96dd795cdd7d71456a19
1 /*
2 This file is part of the KDE libraries
3 Copyright (c) 1999 Waldo Bastian <bastian@kde.org>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License version 2 as published by the Free Software Foundation.
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
20 #define QT_NO_CAST_FROM_ASCII
22 #include "klauncher.h"
23 #include "klauncher_cmds.h"
24 #include "klauncher_adaptor.h"
26 #include <config.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <signal.h>
33 #include <sys/time.h>
35 #ifdef Q_WS_X11
36 #include <kstartupinfo.h>
37 #include <X11/Xlib.h>
38 #endif
40 #include <QtCore/QFile>
42 #include <kconfig.h>
43 #include <kdebug.h>
44 #include <kde_file.h>
45 #include <klibloader.h>
46 #include <klocale.h>
47 #include <kprotocolmanager.h>
48 #include <kprotocolinfo.h>
49 #include <krun.h>
50 #include <kstandarddirs.h>
51 #include <ktemporaryfile.h>
52 #include <kdesktopfile.h>
53 #include <kurl.h>
55 #include <kio/global.h>
56 #include <kio/connection.h>
57 #include <kio/slaveinterface.h>
59 // Dispose slaves after being idle for SLAVE_MAX_IDLE seconds
60 #define SLAVE_MAX_IDLE 30
62 // #define KLAUNCHER_VERBOSE_OUTPUT
64 static const char* const s_DBusStartupTypeToString[] =
65 { "DBusNone", "DBusUnique", "DBusMulti", "DBusWait", "ERROR" };
67 using namespace KIO;
69 IdleSlave::IdleSlave(QObject *parent)
70 : QObject(parent)
72 QObject::connect(&mConn, SIGNAL(readyRead()), this, SLOT(gotInput()));
73 // Send it a SLAVE_STATUS command.
74 mConn.send( CMD_SLAVE_STATUS );
75 mPid = 0;
76 mBirthDate = time(0);
77 mOnHold = false;
80 template<int T> struct PIDType { typedef pid_t PID_t; } ;
81 template<> struct PIDType<2> { typedef qint16 PID_t; } ;
82 template<> struct PIDType<4> { typedef qint32 PID_t; } ;
84 void
85 IdleSlave::gotInput()
87 int cmd;
88 QByteArray data;
89 if (mConn.read( &cmd, data) == -1)
91 // Communication problem with slave.
92 kError(7016) << "SlavePool: No communication with slave." << endl;
93 deleteLater();
95 else if (cmd == MSG_SLAVE_ACK)
97 deleteLater();
99 else if (cmd != MSG_SLAVE_STATUS)
101 kError(7016) << "SlavePool: Unexpected data from slave." << endl;
102 deleteLater();
104 else
106 QDataStream stream( data );
107 PIDType<sizeof(pid_t)>::PID_t stream_pid;
108 pid_t pid;
109 QByteArray protocol;
110 QString host;
111 qint8 b;
112 stream >> stream_pid >> protocol >> host >> b;
113 pid = stream_pid;
114 // Overload with (bool) onHold, (KUrl) url.
115 if (!stream.atEnd())
117 KUrl url;
118 stream >> url;
119 mOnHold = true;
120 mUrl = url;
123 mPid = pid;
124 mConnected = (b != 0);
125 mProtocol = QString::fromLatin1(protocol);
126 mHost = host;
127 emit statusUpdate(this);
131 void
132 IdleSlave::connect(const QString &app_socket)
134 QByteArray data;
135 QDataStream stream( &data, QIODevice::WriteOnly);
136 stream << app_socket;
137 mConn.send( CMD_SLAVE_CONNECT, data );
138 // Timeout!
141 void
142 IdleSlave::reparseConfiguration()
144 mConn.send( CMD_REPARSECONFIGURATION );
147 bool
148 IdleSlave::match(const QString &protocol, const QString &host, bool needConnected)
150 if (mOnHold || protocol != mProtocol) {
151 return false;
153 if (host.isEmpty()) {
154 return true;
156 return (host == mHost) && (!needConnected || mConnected);
159 bool
160 IdleSlave::onHold(const KUrl &url)
162 if (!mOnHold) return false;
163 return (url == mUrl);
167 IdleSlave::age(time_t now)
169 return (int) difftime(now, mBirthDate);
172 static KLauncher* g_klauncher_self;
174 KLauncher::KLauncher(int _kdeinitSocket)
175 : QObject(0),
176 kdeinitSocket(_kdeinitSocket)
178 #ifdef Q_WS_X11
179 mCached_dpy = NULL;
180 #endif
181 Q_ASSERT( g_klauncher_self == NULL );
182 g_klauncher_self = this;
184 mAutoTimer.setSingleShot(true);
185 new KLauncherAdaptor(this);
186 QDBusConnection::sessionBus().registerObject(QLatin1String("/KLauncher"), this); // same as ktoolinvocation.cpp
188 connect(&mAutoTimer, SIGNAL(timeout()), this, SLOT(slotAutoStart()));
189 connect(QDBusConnection::sessionBus().interface(),
190 SIGNAL(serviceOwnerChanged(QString,QString,QString)),
191 SLOT(slotNameOwnerChanged(QString,QString,QString)));
193 mConnectionServer.listenForRemote();
194 connect(&mConnectionServer, SIGNAL(newConnection()), SLOT(acceptSlave()));
195 if (!mConnectionServer.isListening())
197 // Severe error!
198 qDebug("KLauncher: Fatal error, can't create tempfile!");
199 ::_exit(1);
202 connect(&mTimer, SIGNAL(timeout()), SLOT(idleTimeout()));
204 #ifndef Q_WS_WIN
205 kdeinitNotifier = new QSocketNotifier(kdeinitSocket, QSocketNotifier::Read);
206 connect(kdeinitNotifier, SIGNAL( activated( int )),
207 this, SLOT( slotKDEInitData( int )));
208 kdeinitNotifier->setEnabled( true );
209 #endif
210 lastRequest = 0;
211 bProcessingQueue = false;
213 mSlaveDebug = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT"));
214 if (!mSlaveDebug.isEmpty())
216 qWarning("Klauncher running in slave-debug mode for slaves of protocol '%s'", qPrintable(mSlaveDebug));
218 mSlaveValgrind = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND"));
219 if (!mSlaveValgrind.isEmpty())
221 mSlaveValgrindSkin = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND_SKIN"));
222 qWarning("Klauncher running slaves through valgrind for slaves of protocol '%s'", qPrintable(mSlaveValgrind));
224 #ifdef Q_WS_WIN
225 kDebug(7016) << "LAUNCHER_OK";
226 #else
227 klauncher_header request_header;
228 request_header.cmd = LAUNCHER_OK;
229 request_header.arg_length = 0;
230 write(kdeinitSocket, &request_header, sizeof(request_header));
231 #endif
234 KLauncher::~KLauncher()
236 close();
237 g_klauncher_self = NULL;
240 void KLauncher::close()
242 #ifdef Q_WS_X11
243 if( mCached_dpy != NULL )
245 XCloseDisplay( mCached_dpy );
246 mCached_dpy = NULL;
248 #endif
251 void
252 KLauncher::destruct()
254 if (g_klauncher_self)
255 g_klauncher_self->close();
256 // We don't delete the app here, that's intentional.
257 ::_exit(255);
260 void KLauncher::setLaunchEnv(const QString &name, const QString &value)
262 #ifdef Q_WS_WIN
264 #else
265 klauncher_header request_header;
266 QByteArray requestData;
267 requestData.append(name.toLocal8Bit()).append('\0').append(value.toLocal8Bit()).append('\0');
268 request_header.cmd = LAUNCHER_SETENV;
269 request_header.arg_length = requestData.size();
270 write(kdeinitSocket, &request_header, sizeof(request_header));
271 write(kdeinitSocket, requestData.data(), request_header.arg_length);
272 #endif
275 #ifndef Q_WS_WIN
277 * Read 'len' bytes from 'sock' into buffer.
278 * returns -1 on failure, 0 on no data.
280 static int
281 read_socket(int sock, char *buffer, int len)
283 ssize_t result;
284 int bytes_left = len;
285 while (bytes_left > 0) {
286 // in case we get a request to start an application and data arrive
287 // to kdeinitSocket at the same time, requestStart() will already
288 // call slotKDEInitData(), so we must check there's still something
289 // to read, otherwise this would block
291 // Same thing if kdeinit dies without warning.
293 fd_set in;
294 timeval tm = { 30, 0 }; // 30 seconds timeout, so we're not stuck in case kdeinit dies on us
295 FD_ZERO ( &in );
296 FD_SET( sock, &in );
297 select( sock + 1, &in, 0, 0, &tm );
298 if( !FD_ISSET( sock, &in )) {
299 kDebug(7016) << "read_socket" << sock << "nothing to read, kdeinit4 must be dead";
300 return -1;
303 result = read(sock, buffer, bytes_left);
304 if (result > 0)
306 buffer += result;
307 bytes_left -= result;
309 else if (result == 0)
310 return -1;
311 else if ((result == -1) && (errno != EINTR))
312 return -1;
314 return 0;
318 void
319 KLauncher::slotKDEInitData(int)
321 klauncher_header request_header;
322 QByteArray requestData;
324 int result = read_socket(kdeinitSocket, (char *) &request_header,
325 sizeof( request_header));
326 if (result == -1)
328 kDebug(7016) << "Exiting on read_socket errno:" << errno;
329 KDE_signal( SIGHUP, SIG_IGN);
330 KDE_signal( SIGTERM, SIG_IGN);
331 destruct(); // Exit!
333 requestData.resize(request_header.arg_length);
334 result = read_socket(kdeinitSocket, (char *) requestData.data(),
335 request_header.arg_length);
337 processRequestReturn(request_header.cmd,requestData);
340 #endif
342 void KLauncher::processRequestReturn(int status, const QByteArray &requestData)
344 if (status == LAUNCHER_CHILD_DIED)
346 long *request_data;
347 request_data = (long *) requestData.data();
348 processDied(request_data[0], request_data[1]);
349 return;
351 if (lastRequest && (status == LAUNCHER_OK))
353 long *request_data;
354 request_data = (long *) requestData.data();
355 lastRequest->pid = (pid_t) (*request_data);
356 kDebug(7016).nospace() << lastRequest->name << " (pid " << lastRequest->pid <<
357 ") up and running.";
358 switch(lastRequest->dbus_startup_type)
360 case KService::DBusNone:
361 lastRequest->status = KLaunchRequest::Running;
362 break;
363 case KService::DBusUnique:
364 case KService::DBusWait:
365 case KService::DBusMulti:
366 lastRequest->status = KLaunchRequest::Launching;
367 break;
369 lastRequest = 0;
370 return;
372 if (lastRequest && (status == LAUNCHER_ERROR))
374 lastRequest->status = KLaunchRequest::Error;
375 kDebug(7016) << lastRequest->name << " failed." << endl;
376 if (!requestData.isEmpty())
377 lastRequest->errorMsg = QString::fromUtf8((char *) requestData.data());
378 lastRequest = 0;
379 return;
382 kWarning(7016)<< "Unexpected request return" << (unsigned int) status;
385 void
386 KLauncher::processDied(pid_t pid, long exitStatus)
388 #ifdef KLAUNCHER_VERBOSE_OUTPUT
389 kDebug(7016) << pid << "exitStatus=" << exitStatus;
390 #else
391 Q_UNUSED(exitStatus);
392 // We should probably check the exitStatus for the uniqueapp case?
393 #endif
394 foreach (KLaunchRequest *request, requestList)
396 #ifdef KLAUNCHER_VERBOSE_OUTPUT
397 kDebug(7016) << " had pending request" << request->pid;
398 #endif
399 if (request->pid == pid)
401 if (request->dbus_startup_type == KService::DBusWait)
402 request->status = KLaunchRequest::Done;
403 else if ((request->dbus_startup_type == KService::DBusUnique)
404 && QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) {
405 request->status = KLaunchRequest::Running;
406 #ifdef KLAUNCHER_VERBOSE_OUTPUT
407 kDebug(7016) << pid << "running as a unique app";
408 #endif
409 } else {
410 request->status = KLaunchRequest::Error;
411 #ifdef KLAUNCHER_VERBOSE_OUTPUT
412 kDebug(7016) << pid << "died, requestDone. status=" << request->status;
413 #endif
415 requestDone(request);
416 return;
419 #ifdef KLAUNCHER_VERBOSE_OUTPUT
420 kDebug(7016) << "found no pending requests for PID" << pid;
421 #endif
424 static bool matchesPendingRequest(const QString& appId, const QString& pendingAppId)
426 // appId just registered, e.g. org.koffice.kword-12345
427 // Let's see if this is what pendingAppId (e.g. org.koffice.kword or *.kword) was waiting for.
429 const QString newAppId = appId.left(appId.lastIndexOf(QLatin1Char('-'))); // strip out the -12345 if present.
431 //kDebug() << "appId=" << appId << "newAppId=" << newAppId << "pendingAppId=" << pendingAppId;
433 if (pendingAppId.startsWith(QLatin1String("*."))) {
434 const QString pendingName = pendingAppId.mid(2);
435 const QString appName = newAppId.mid(newAppId.lastIndexOf(QLatin1Char('.'))+1);
436 //kDebug() << "appName=" << appName;
437 return appName == pendingName;
440 return newAppId == pendingAppId;
443 void
444 KLauncher::slotNameOwnerChanged(const QString &appId, const QString &oldOwner,
445 const QString &newOwner)
447 Q_UNUSED(oldOwner);
448 if (appId.isEmpty() || newOwner.isEmpty())
449 return;
451 #ifdef KLAUNCHER_VERBOSE_OUTPUT
452 kDebug(7016) << "new app" << appId;
453 #endif
454 foreach (KLaunchRequest *request, requestList)
456 if (request->status != KLaunchRequest::Launching)
457 continue;
459 #ifdef KLAUNCHER_VERBOSE_OUTPUT
460 kDebug(7016) << "had pending request" << request->name << s_DBusStartupTypeToString[request->dbus_startup_type] << "dbus_name" << request->dbus_name << request->tolerant_dbus_name;
461 #endif
462 // For unique services check the requested service name first
463 if (request->dbus_startup_type == KService::DBusUnique) {
464 if ((appId == request->dbus_name) || // just started
465 QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { // was already running
466 request->status = KLaunchRequest::Running;
467 #ifdef KLAUNCHER_VERBOSE_OUTPUT
468 kDebug(7016) << "OK, unique app" << request->dbus_name << "is running";
469 #endif
470 requestDone(request);
471 continue;
472 } else {
473 #ifdef KLAUNCHER_VERBOSE_OUTPUT
474 kDebug(7016) << "unique app" << request->dbus_name << "not running yet";
475 #endif
479 const QString rAppId = !request->tolerant_dbus_name.isEmpty() ? request->tolerant_dbus_name : request->dbus_name;
480 #ifdef KLAUNCHER_VERBOSE_OUTPUT
481 //kDebug(7016) << "using" << rAppId << "for matching";
482 #endif
483 if (rAppId.isEmpty())
484 continue;
486 if (matchesPendingRequest(appId, rAppId)) {
487 #ifdef KLAUNCHER_VERBOSE_OUTPUT
488 kDebug(7016) << "ok, request done";
489 #endif
490 request->dbus_name = appId;
491 request->status = KLaunchRequest::Running;
492 requestDone(request);
493 continue;
498 void
499 KLauncher::autoStart(int phase)
501 if( mAutoStart.phase() >= phase )
502 return;
503 mAutoStart.setPhase(phase);
504 if (phase == 0)
505 mAutoStart.loadAutoStartList();
506 mAutoTimer.start(0);
509 void
510 KLauncher::slotAutoStart()
512 KService::Ptr s;
515 QString service = mAutoStart.startService();
516 if (service.isEmpty())
518 // Done
519 if( !mAutoStart.phaseDone())
521 mAutoStart.setPhaseDone();
522 switch( mAutoStart.phase())
524 case 0:
525 emit autoStart0Done();
526 break;
527 case 1:
528 emit autoStart1Done();
529 break;
530 case 2:
531 emit autoStart2Done();
532 break;
535 return;
537 s = new KService(service);
539 while (!start_service(s, QStringList(), QStringList(), "0", false, true, QDBusMessage()));
540 // Loop till we find a service that we can start.
543 void
544 KLauncher::requestDone(KLaunchRequest *request)
546 if ((request->status == KLaunchRequest::Running) ||
547 (request->status == KLaunchRequest::Done))
549 requestResult.result = 0;
550 requestResult.dbusName = request->dbus_name;
551 requestResult.error = QString::fromLatin1(""); // not null, cf assert further down
552 requestResult.pid = request->pid;
554 else
556 requestResult.result = 1;
557 requestResult.dbusName = QString();
558 requestResult.error = i18n("KDEInit could not launch '%1'.", request->name);
559 if (!request->errorMsg.isEmpty())
560 requestResult.error += QString::fromLatin1(":\n") + request->errorMsg;
561 requestResult.pid = 0;
563 #ifdef Q_WS_X11
564 if (!request->startup_dpy.isEmpty())
566 Display* dpy = NULL;
567 if( (mCached_dpy != NULL) &&
568 (request->startup_dpy == XDisplayString( mCached_dpy )))
569 dpy = mCached_dpy;
570 if( dpy == NULL )
571 dpy = XOpenDisplay(request->startup_dpy);
572 if( dpy )
574 KStartupInfoId id;
575 id.initId(request->startup_id);
576 KStartupInfo::sendFinishX( dpy, id );
577 if( mCached_dpy != dpy && mCached_dpy != NULL )
578 XCloseDisplay( mCached_dpy );
579 mCached_dpy = dpy;
582 #endif
585 if (request->autoStart)
587 mAutoTimer.start(0);
590 if (request->transaction.type() != QDBusMessage::InvalidMessage)
592 if ( requestResult.dbusName.isNull() ) // null strings can't be sent
593 requestResult.dbusName = QString();
594 Q_ASSERT( !requestResult.error.isNull() );
595 PIDType<sizeof(pid_t)>::PID_t stream_pid = requestResult.pid;
596 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << requestResult.result
597 << requestResult.dbusName
598 << requestResult.error
599 << stream_pid));
601 #ifdef KLAUNCHER_VERBOSE_OUTPUT
602 kDebug(7016) << "removing done request" << request->name << "PID" << request->pid;
603 #endif
605 requestList.removeAll( request );
606 delete request;
609 static void appendLong(QByteArray &ba, long l)
611 const int sz = ba.size();
612 ba.resize(sz + sizeof(long));
613 memcpy(ba.data() + sz, &l, sizeof(long));
616 void
617 KLauncher::requestStart(KLaunchRequest *request)
619 #ifdef Q_WS_WIN
620 requestList.append( request );
621 lastRequest = request;
623 KProcess *process = new KProcess;
624 process->setOutputChannelMode(KProcess::MergedChannels);
625 connect(process ,SIGNAL(readyReadStandardOutput()),this, SLOT(slotGotOutput()) );
626 connect(process ,SIGNAL(finished(int, QProcess::ExitStatus)),this, SLOT(slotFinished(int, QProcess::ExitStatus)) );
627 request->process = process;
629 // process.setEnvironment(envlist);
630 QStringList args;
631 foreach (const QString &arg, request->arg_list)
632 args << arg;
634 process->setProgram(request->name,args);
635 process->start();
637 if (!process->waitForStarted())
639 processRequestReturn(LAUNCHER_ERROR,"");
641 else
643 request->pid = process->pid();
644 QByteArray data((char *)&request->pid, sizeof(int));
645 processRequestReturn(LAUNCHER_OK,data);
647 return;
649 #else
650 requestList.append( request );
651 // Send request to kdeinit.
652 klauncher_header request_header;
653 QByteArray requestData;
654 requestData.reserve(1024);
656 appendLong(requestData, request->arg_list.count() + 1);
657 requestData.append(request->name.toLocal8Bit());
658 requestData.append('\0');
659 foreach (const QString &arg, request->arg_list)
660 requestData.append(arg.toLocal8Bit()).append('\0');
661 appendLong(requestData, request->envs.count());
662 foreach (const QString &env, request->envs)
663 requestData.append(env.toLocal8Bit()).append('\0');
664 appendLong(requestData, 0); // avoid_loops, always false here
665 #ifdef Q_WS_X11
666 bool startup_notify = !request->startup_id.isNull() && request->startup_id != "0";
667 if( startup_notify )
668 requestData.append(request->startup_id).append('\0');
669 #endif
670 if (!request->cwd.isEmpty())
671 requestData.append(QFile::encodeName(request->cwd)).append('\0');
673 #ifdef Q_WS_X11
674 request_header.cmd = startup_notify ? LAUNCHER_EXT_EXEC : LAUNCHER_EXEC_NEW;
675 #else
676 request_header.cmd = LAUNCHER_EXEC_NEW;
677 #endif
678 request_header.arg_length = requestData.length();
680 #ifdef KLAUNCHER_VERBOSE_OUTPUT
681 kDebug(7016) << "Asking kdeinit to start" << request->name << request->arg_list
682 << "cmd=" << commandToString(request_header.cmd);
683 #endif
685 write(kdeinitSocket, &request_header, sizeof(request_header));
686 write(kdeinitSocket, requestData.data(), requestData.length());
688 // Wait for pid to return.
689 lastRequest = request;
690 do {
691 slotKDEInitData( kdeinitSocket );
693 while (lastRequest != 0);
694 #endif
697 void KLauncher::exec_blind(const QString &name, const QStringList &arg_list, const QStringList &envs, const QString &startup_id)
699 KLaunchRequest *request = new KLaunchRequest;
700 request->autoStart = false;
701 request->name = name;
702 request->arg_list = arg_list;
703 request->dbus_startup_type = KService::DBusNone;
704 request->pid = 0;
705 request->status = KLaunchRequest::Launching;
706 request->envs = envs;
707 // Find service, if any - strip path if needed
708 KService::Ptr service = KService::serviceByDesktopName( name.mid( name.lastIndexOf(QLatin1Char('/')) + 1 ));
709 if (service)
710 send_service_startup_info(request, service, startup_id.toLocal8Bit(), QStringList());
711 else // no .desktop file, no startup info
712 cancel_service_startup_info( request, startup_id.toLocal8Bit(), envs );
714 requestStart(request);
715 // We don't care about this request any longer....
716 requestDone(request);
720 // KDE5: remove
721 bool
722 KLauncher::start_service_by_name(const QString &serviceName, const QStringList &urls,
723 const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
725 KService::Ptr service;
726 // Find service
727 service = KService::serviceByName(serviceName);
728 if (!service)
730 requestResult.result = ENOENT;
731 requestResult.error = i18n("Could not find service '%1'.", serviceName);
732 cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
733 return false;
735 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
738 bool
739 KLauncher::start_service_by_desktop_path(const QString &serviceName, const QStringList &urls,
740 const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
742 KService::Ptr service;
743 // Find service
744 if (QFileInfo(serviceName).isAbsolute() && QFile::exists(serviceName))
746 // Full path
747 service = new KService(serviceName);
749 else
751 service = KService::serviceByDesktopPath(serviceName);
753 if (!service)
755 requestResult.result = ENOENT;
756 requestResult.error = i18n("Could not find service '%1'.", serviceName);
757 cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
758 return false;
760 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
763 bool
764 KLauncher::start_service_by_desktop_name(const QString &serviceName, const QStringList &urls,
765 const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
767 KService::Ptr service = KService::serviceByDesktopName(serviceName);
768 if (!service)
770 requestResult.result = ENOENT;
771 requestResult.error = i18n("Could not find service '%1'.", serviceName);
772 cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
773 return false;
775 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
778 bool
779 KLauncher::start_service(KService::Ptr service, const QStringList &_urls,
780 const QStringList &envs, const QByteArray &startup_id,
781 bool blind, bool autoStart, const QDBusMessage &msg)
783 QStringList urls = _urls;
784 bool runPermitted = KDesktopFile::isAuthorizedDesktopFile(service->entryPath());
786 if (!service->isValid() || !runPermitted)
788 requestResult.result = ENOEXEC;
789 if (service->isValid())
790 requestResult.error = i18n("Service '%1' must be executable to run.", service->entryPath());
791 else
792 requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
793 cancel_service_startup_info( NULL, startup_id, envs ); // cancel it if any
794 return false;
796 KLaunchRequest *request = new KLaunchRequest;
797 request->autoStart = autoStart;
799 if ((urls.count() > 1) && !service->allowMultipleFiles())
801 // We need to launch the application N times. That sucks.
802 // We ignore the result for application 2 to N.
803 // For the first file we launch the application in the
804 // usual way. The reported result is based on this
805 // application.
806 QStringList::ConstIterator it = urls.constBegin();
807 for(++it;
808 it != urls.constEnd();
809 ++it)
811 QStringList singleUrl;
812 singleUrl.append(*it);
813 QByteArray startup_id2 = startup_id;
814 if( !startup_id2.isEmpty() && startup_id2 != "0" )
815 startup_id2 = "0"; // can't use the same startup_id several times
816 start_service( service, singleUrl, envs, startup_id2, true, false, msg);
818 QString firstURL = *(urls.begin());
819 urls.clear();
820 urls.append(firstURL);
822 createArgs(request, service, urls);
824 // We must have one argument at least!
825 if (!request->arg_list.count())
827 requestResult.result = ENOEXEC;
828 requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
829 delete request;
830 cancel_service_startup_info( NULL, startup_id, envs );
831 return false;
834 request->name = request->arg_list.takeFirst();
836 if (request->name.endsWith(QLatin1String("/kioexec"))) {
837 // Special case for kioexec; if createArgs said we were going to use it,
838 // then we have to expect a kioexec-PID, not a org.kde.finalapp...
839 // Testcase: konqueror www.kde.org, RMB on link, open with, kruler.
841 request->dbus_startup_type = KService::DBusMulti;
842 request->dbus_name = QString::fromLatin1("org.kde.kioexec");
843 } else {
844 request->dbus_startup_type = service->dbusStartupType();
846 if ((request->dbus_startup_type == KService::DBusUnique) ||
847 (request->dbus_startup_type == KService::DBusMulti)) {
848 const QVariant v = service->property(QLatin1String("X-DBUS-ServiceName"));
849 if (v.isValid()) {
850 request->dbus_name = v.toString();
852 if (request->dbus_name.isEmpty()) {
853 const QString binName = KRun::binaryName(service->exec(), true);
854 request->dbus_name = QString::fromLatin1("org.kde.") + binName;
855 request->tolerant_dbus_name = QString::fromLatin1("*.") + binName;
860 #ifdef KLAUNCHER_VERBOSE_OUTPUT
861 kDebug(7016) << "name=" << request->name << "dbus_name=" << request->dbus_name
862 << "startup type=" << s_DBusStartupTypeToString[request->dbus_startup_type];
863 #endif
865 request->pid = 0;
866 request->envs = envs;
867 send_service_startup_info( request, service, startup_id, envs );
869 // Request will be handled later.
870 if (!blind && !autoStart)
872 msg.setDelayedReply(true);
873 request->transaction = msg;
875 queueRequest(request);
876 return true;
879 void
880 KLauncher::send_service_startup_info( KLaunchRequest *request, KService::Ptr service, const QByteArray& startup_id,
881 const QStringList &envs )
883 #ifdef Q_WS_X11
884 request->startup_id = "0";
885 if (startup_id == "0")
886 return;
887 bool silent;
888 QByteArray wmclass;
889 if( !KRun::checkStartupNotify( QString(), service.data(), &silent, &wmclass ))
890 return;
891 KStartupInfoId id;
892 id.initId(startup_id);
893 QByteArray dpy_str;
894 foreach (const QString &env, envs) {
895 if (env.startsWith(QLatin1String("DISPLAY=")))
896 dpy_str = env.mid(8).toLocal8Bit();
898 Display* dpy = NULL;
899 if (!dpy_str.isEmpty() && mCached_dpy != NULL && dpy_str != XDisplayString(mCached_dpy))
900 dpy = mCached_dpy;
901 if (dpy == NULL)
902 dpy = XOpenDisplay(dpy_str);
903 request->startup_id = id.id();
904 if (dpy == NULL) {
905 cancel_service_startup_info( request, startup_id, envs );
906 return;
909 request->startup_dpy = dpy_str;
911 KStartupInfoData data;
912 data.setName( service->name());
913 data.setIcon( service->icon());
914 data.setDescription( i18n( "Launching %1" , service->name()));
915 if( !wmclass.isEmpty())
916 data.setWMClass( wmclass );
917 if( silent )
918 data.setSilent( KStartupInfoData::Yes );
919 // the rest will be sent by kdeinit
920 KStartupInfo::sendStartupX( dpy, id, data );
921 if( mCached_dpy != dpy && mCached_dpy != NULL )
922 XCloseDisplay( mCached_dpy );
923 mCached_dpy = dpy;
924 return;
925 #else
926 return;
927 #endif
930 void
931 KLauncher::cancel_service_startup_info( KLaunchRequest* request, const QByteArray& startup_id,
932 const QStringList &envs )
934 #ifdef Q_WS_X11
935 if( request != NULL )
936 request->startup_id = "0";
937 if( !startup_id.isEmpty() && startup_id != "0" )
939 QString dpy_str;
940 foreach (const QString &env, envs) {
941 if (env.startsWith(QLatin1String("DISPLAY=")))
942 dpy_str = env.mid(8);
944 Display* dpy = NULL;
945 if( !dpy_str.isEmpty() && mCached_dpy != NULL
946 && dpy_str != QLatin1String(XDisplayString( mCached_dpy )) )
947 dpy = mCached_dpy;
948 if( dpy == NULL )
949 dpy = XOpenDisplay( dpy_str.toLatin1().constData() );
950 if( dpy == NULL )
951 return;
952 KStartupInfoId id;
953 id.initId(startup_id);
954 KStartupInfo::sendFinishX( dpy, id );
955 if( mCached_dpy != dpy && mCached_dpy != NULL )
956 XCloseDisplay( mCached_dpy );
957 mCached_dpy = dpy;
959 #endif
962 bool
963 KLauncher::kdeinit_exec(const QString &app, const QStringList &args,
964 const QString& workdir, const QStringList &envs,
965 const QString &startup_id, bool wait, const QDBusMessage &msg)
967 KLaunchRequest *request = new KLaunchRequest;
968 request->autoStart = false;
969 request->arg_list = args;
970 request->name = app;
971 if (wait)
972 request->dbus_startup_type = KService::DBusWait;
973 else
974 request->dbus_startup_type = KService::DBusNone;
975 request->pid = 0;
976 #ifdef Q_WS_X11
977 request->startup_id = startup_id.toLocal8Bit();
978 #endif
979 request->envs = envs;
980 request->cwd = workdir;
981 #ifdef Q_WS_X11
982 if (!app.endsWith(QLatin1String("kbuildsycoca4"))) { // avoid stupid loop
983 // Find service, if any - strip path if needed
984 const QString desktopName = app.mid(app.lastIndexOf(QLatin1Char('/')) + 1);
985 KService::Ptr service = KService::serviceByDesktopName(desktopName);
986 #ifdef Q_WS_X11
987 if (service)
988 send_service_startup_info(request, service,
989 request->startup_id, QStringList());
990 else // no .desktop file, no startup info
991 cancel_service_startup_info(request, request->startup_id, envs);
992 #endif
994 #endif
995 msg.setDelayedReply(true);
996 request->transaction = msg;
997 queueRequest(request);
998 return true;
1001 void
1002 KLauncher::queueRequest(KLaunchRequest *request)
1004 requestQueue.append( request );
1005 if (!bProcessingQueue)
1007 bProcessingQueue = true;
1008 QTimer::singleShot(0, this, SLOT( slotDequeue() ));
1012 void
1013 KLauncher::slotDequeue()
1015 do {
1016 KLaunchRequest *request = requestQueue.takeFirst();
1017 // process request
1018 request->status = KLaunchRequest::Launching;
1019 requestStart(request);
1020 if (request->status != KLaunchRequest::Launching)
1022 // Request handled.
1023 #ifdef KLAUNCHER_VERBOSE_OUTPUT
1024 kDebug(7016) << "Request handled already";
1025 #endif
1026 requestDone( request );
1027 continue;
1029 } while(requestQueue.count());
1030 bProcessingQueue = false;
1033 void
1034 KLauncher::createArgs( KLaunchRequest *request, const KService::Ptr service ,
1035 const QStringList &urls)
1037 const QStringList params = KRun::processDesktopExec(*service, urls);
1039 for(QStringList::ConstIterator it = params.begin();
1040 it != params.end(); ++it)
1042 request->arg_list.append(*it);
1044 request->cwd = service->path();
1047 ///// IO-Slave functions
1049 pid_t
1050 KLauncher::requestHoldSlave(const KUrl &url, const QString &app_socket)
1052 IdleSlave *slave = 0;
1053 foreach (IdleSlave *p, mSlaveList)
1055 if (p->onHold(url))
1057 slave = p;
1058 break;
1061 if (slave)
1063 mSlaveList.removeAll(slave);
1064 slave->connect(app_socket);
1065 return slave->pid();
1067 return 0;
1071 pid_t
1072 KLauncher::requestSlave(const QString &protocol,
1073 const QString &host,
1074 const QString &app_socket,
1075 QString &error)
1077 IdleSlave *slave = 0;
1078 foreach (IdleSlave *p, mSlaveList)
1080 if (p->match(protocol, host, true))
1082 slave = p;
1083 break;
1086 if (!slave)
1088 foreach (IdleSlave *p, mSlaveList)
1090 if (p->match(protocol, host, false))
1092 slave = p;
1093 break;
1097 if (!slave)
1099 foreach (IdleSlave *p, mSlaveList)
1101 if (p->match(protocol, QString(), false))
1103 slave = p;
1104 break;
1108 if (slave)
1110 mSlaveList.removeAll(slave);
1111 slave->connect(app_socket);
1112 return slave->pid();
1115 QString name = KProtocolInfo::exec(protocol);
1116 if (name.isEmpty())
1118 error = i18n("Unknown protocol '%1'.\n", protocol);
1119 return 0;
1122 QStringList arg_list;
1123 #ifdef Q_WS_WIN
1124 arg_list << name;
1125 arg_list << protocol;
1126 arg_list << mConnectionServer.address();
1127 arg_list << app_socket;
1128 name = KStandardDirs::findExe(QLatin1String("kioslave"));
1129 #else
1130 QString arg1 = protocol;
1131 QString arg2 = mConnectionServer.address();
1132 QString arg3 = app_socket;
1133 arg_list.append(arg1);
1134 arg_list.append(arg2);
1135 arg_list.append(arg3);
1136 #endif
1138 kDebug(7016) << "KLauncher: launching new slave " << name << " with protocol=" << protocol
1139 << " args=" << arg_list << endl;
1141 #ifdef Q_OS_UNIX
1142 if (mSlaveDebug == arg1)
1144 klauncher_header request_header;
1145 request_header.cmd = LAUNCHER_DEBUG_WAIT;
1146 request_header.arg_length = 0;
1147 write(kdeinitSocket, &request_header, sizeof(request_header));
1149 if (mSlaveValgrind == arg1)
1151 arg_list.prepend(KLibLoader::findLibrary(name));
1152 arg_list.prepend(KStandardDirs::locate("exe", QString::fromLatin1("kioslave")));
1153 name = QString::fromLatin1("valgrind");
1154 if (!mSlaveValgrindSkin.isEmpty()) {
1155 arg_list.prepend(QLatin1String("--tool=") + mSlaveValgrindSkin);
1156 } else
1157 arg_list.prepend(QLatin1String("--tool=memcheck"));
1159 #endif
1160 KLaunchRequest *request = new KLaunchRequest;
1161 request->autoStart = false;
1162 request->name = name;
1163 request->arg_list = arg_list;
1164 request->dbus_startup_type = KService::DBusNone;
1165 request->pid = 0;
1166 #ifdef Q_WS_X11
1167 request->startup_id = "0";
1168 #endif
1169 request->status = KLaunchRequest::Launching;
1170 requestStart(request);
1171 pid_t pid = request->pid;
1173 // kDebug(7016) << "Slave launched, pid = " << pid;
1175 // We don't care about this request any longer....
1176 requestDone(request);
1177 if (!pid)
1179 error = i18n("Error loading '%1'.\n", name);
1181 return pid;
1184 void
1185 KLauncher::waitForSlave(int pid, const QDBusMessage &msg)
1187 foreach (IdleSlave *slave, mSlaveList)
1189 if (slave->pid() == static_cast<pid_t>(pid))
1190 return; // Already here.
1192 SlaveWaitRequest *waitRequest = new SlaveWaitRequest;
1193 msg.setDelayedReply(true);
1194 waitRequest->transaction = msg;
1195 waitRequest->pid = static_cast<pid_t>(pid);
1196 mSlaveWaitRequest.append(waitRequest);
1199 void
1200 KLauncher::acceptSlave()
1202 IdleSlave *slave = new IdleSlave(this);
1203 mConnectionServer.setNextPendingConnection(&slave->mConn);
1204 mSlaveList.append(slave);
1205 connect(slave, SIGNAL(destroyed()), this, SLOT(slotSlaveGone()));
1206 connect(slave, SIGNAL(statusUpdate(IdleSlave *)),
1207 this, SLOT(slotSlaveStatus(IdleSlave *)));
1208 if (!mTimer.isActive())
1210 mTimer.start(1000*10);
1214 void
1215 KLauncher::slotSlaveStatus(IdleSlave *slave)
1217 QMutableListIterator<SlaveWaitRequest *> it(mSlaveWaitRequest);
1218 while(it.hasNext())
1220 SlaveWaitRequest *waitRequest = it.next();
1221 if (waitRequest->pid == slave->pid())
1223 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply());
1224 it.remove();
1225 delete waitRequest;
1230 void
1231 KLauncher::slotSlaveGone()
1233 IdleSlave *slave = (IdleSlave *) sender();
1234 mSlaveList.removeAll(slave);
1235 if ((mSlaveList.count() == 0) && (mTimer.isActive()))
1237 mTimer.stop();
1241 void
1242 KLauncher::idleTimeout()
1244 bool keepOneFileSlave=true;
1245 time_t now = time(0);
1246 foreach (IdleSlave *slave, mSlaveList)
1248 if ((slave->protocol()==QLatin1String("file")) && (keepOneFileSlave))
1249 keepOneFileSlave=false;
1250 else if (slave->age(now) > SLAVE_MAX_IDLE)
1252 // killing idle slave
1253 delete slave;
1258 void KLauncher::reparseConfiguration()
1260 KProtocolManager::reparseConfiguration();
1261 foreach (IdleSlave *slave, mSlaveList)
1262 slave->reparseConfiguration();
1265 #ifdef Q_WS_WIN
1266 void
1267 KLauncher::slotGotOutput()
1269 KProcess *p = static_cast<KProcess *>(sender());
1270 QByteArray _stdout = p->readAllStandardOutput();
1271 kDebug(7016) << _stdout.data();
1274 void
1275 KLauncher::slotFinished(int exitCode, QProcess::ExitStatus exitStatus )
1277 KProcess *p = static_cast<KProcess *>(sender());
1278 kDebug(7016) << "process finished exitcode=" << exitCode << "exitStatus=" << exitStatus;
1280 foreach (KLaunchRequest *request, requestList)
1282 if (request->process == p)
1284 #ifdef KLAUNCHER_VERBOSE_OUTPUT
1285 kDebug(7016) << "found KProcess, request done";
1286 #endif
1287 if (exitCode == 0 && exitStatus == QProcess::NormalExit)
1288 request->status = KLaunchRequest::Done;
1289 else
1290 request->status = KLaunchRequest::Error;
1291 requestDone(request);
1292 request->process = 0;
1295 delete p;
1297 #endif
1299 #include "klauncher.moc"