File indexing completed on 2024-04-28 15:25:51
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 1999 Waldo Bastian <bastian@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #ifndef QT_NO_CAST_FROM_ASCII 0009 #define QT_NO_CAST_FROM_ASCII 0010 #endif 0011 0012 #include "klauncher.h" 0013 #include "klauncher_cmds.h" 0014 #include "klauncher_adaptor.h" 0015 #include "kslavelauncheradaptor.h" 0016 #include <klauncher_debug.h> 0017 0018 #include <stdio.h> 0019 #include <qplatformdefs.h> 0020 #include <signal.h> 0021 #include <cerrno> 0022 0023 #if HAVE_X11 0024 #include <kstartupinfo.h> 0025 #include <QGuiApplication> 0026 #endif 0027 0028 #if HAVE_XCB 0029 #include <xcb/xcb.h> 0030 #endif 0031 0032 #include <QDBusConnectionInterface> 0033 #include <QFile> 0034 #include <QFileInfo> 0035 #include <QUrl> 0036 #include <qplatformdefs.h> 0037 0038 #include <KConfig> 0039 #include <QDebug> 0040 #include <KLocalizedString> 0041 #include <KDesktopFile> 0042 #include <KPluginLoader> // to find kioslave modules 0043 #include <KProtocolManager> 0044 #include <KProtocolInfo> 0045 #include <KRun> // TODO port away from kiofilewidgets 0046 0047 #include <kio/desktopexecparser.h> 0048 #include <kio/global.h> 0049 #include <kio/slaveinterface.h> 0050 #include <kiogui_export.h> 0051 0052 #ifdef Q_OS_WIN 0053 #include <qt_windows.h> 0054 //windows.h feels like defining this... 0055 #undef interface 0056 #endif 0057 0058 // Dispose slaves after being idle for SLAVE_MAX_IDLE seconds 0059 #define SLAVE_MAX_IDLE 30 0060 0061 static const char *const s_DBusStartupTypeToString[] = 0062 { "DBusNone", "DBusUnique", "DBusMulti", "ERROR" }; 0063 0064 using namespace KIO; 0065 0066 static KLauncher *g_klauncher_self; 0067 0068 // From qcore_unix_p.h. We could also port to QLocalSocket :) 0069 #define K_EINTR_LOOP(var, cmd) \ 0070 do { \ 0071 var = cmd; \ 0072 } while (var == -1 && errno == EINTR) 0073 0074 ssize_t kde_safe_write(int fd, const void *buf, size_t count) 0075 { 0076 ssize_t ret = 0; 0077 K_EINTR_LOOP(ret, QT_WRITE(fd, buf, count)); 0078 if (ret < 0) { 0079 qCWarning(KLAUNCHER) << "write failed:" << strerror(errno); 0080 } 0081 return ret; 0082 } 0083 0084 #ifndef USE_KPROCESS_FOR_KIOSLAVES 0085 KLauncher::KLauncher(int _kdeinitSocket) 0086 : QObject(nullptr), 0087 kdeinitSocket(_kdeinitSocket) 0088 #else 0089 KLauncher::KLauncher() 0090 : QObject(0) 0091 #endif 0092 { 0093 #if HAVE_X11 0094 mIsX11 = QGuiApplication::platformName() == QStringLiteral("xcb"); 0095 #endif 0096 Q_ASSERT(g_klauncher_self == nullptr); 0097 g_klauncher_self = this; 0098 0099 new KLauncherAdaptor(this); 0100 mSlaveLauncherAdaptor = new KSlaveLauncherAdaptor(this); 0101 QDBusConnection::sessionBus().registerObject(QStringLiteral("/KLauncher"), this); // same as ktoolinvocation.cpp 0102 0103 connect(QDBusConnection::sessionBus().interface(), 0104 SIGNAL(serviceOwnerChanged(QString,QString,QString)), 0105 SLOT(slotNameOwnerChanged(QString,QString,QString))); 0106 0107 mConnectionServer.listenForRemote(); 0108 connect(&mConnectionServer, SIGNAL(newConnection()), SLOT(acceptSlave())); 0109 if (!mConnectionServer.isListening()) { 0110 // Severe error! 0111 qCWarning(KLAUNCHER, "KLauncher: Fatal error, can't create tempfile!"); 0112 ::_exit(1); 0113 } 0114 0115 connect(&mTimer, SIGNAL(timeout()), SLOT(idleTimeout())); 0116 0117 #ifndef USE_KPROCESS_FOR_KIOSLAVES 0118 kdeinitNotifier = new QSocketNotifier(kdeinitSocket, QSocketNotifier::Read); 0119 connect(kdeinitNotifier, SIGNAL(activated(int)), 0120 this, SLOT(slotKDEInitData(int))); 0121 kdeinitNotifier->setEnabled(true); 0122 #endif 0123 lastRequest = nullptr; 0124 bProcessingQueue = false; 0125 0126 mSlaveDebug = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT")); 0127 if (!mSlaveDebug.isEmpty()) { 0128 #ifndef USE_KPROCESS_FOR_KIOSLAVES 0129 qCWarning(KLAUNCHER, "Klauncher running in slave-debug mode for slaves of protocol '%s'", qPrintable(mSlaveDebug)); 0130 #else 0131 // Slave debug mode causes kdeinit to suspend the process waiting 0132 // for the developer to attach gdb to the process; we do not have 0133 // a good way of doing a similar thing if we are using QProcess. 0134 mSlaveDebug.clear(); 0135 qCWarning(KLAUNCHER, "slave-debug mode is not available as Klauncher is not using kdeinit"); 0136 #endif 0137 } 0138 mSlaveValgrind = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND")); 0139 if (!mSlaveValgrind.isEmpty()) { 0140 mSlaveValgrindSkin = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND_SKIN")); 0141 qCWarning(KLAUNCHER, "Klauncher running slaves through valgrind for slaves of protocol '%s'", qPrintable(mSlaveValgrind)); 0142 } 0143 #ifdef USE_KPROCESS_FOR_KIOSLAVES 0144 qCDebug(KLAUNCHER) << "LAUNCHER_OK"; 0145 #else 0146 klauncher_header request_header; 0147 request_header.cmd = LAUNCHER_OK; 0148 request_header.arg_length = 0; 0149 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); 0150 #endif 0151 } 0152 0153 KLauncher::~KLauncher() 0154 { 0155 close(); 0156 g_klauncher_self = nullptr; 0157 } 0158 0159 void KLauncher::close() 0160 { 0161 #if HAVE_XCB 0162 if (mCached) { 0163 xcb_disconnect(mCached.conn); 0164 mCached = XCBConnection(); 0165 } 0166 #endif 0167 } 0168 0169 void 0170 KLauncher::destruct() 0171 { 0172 if (g_klauncher_self) { 0173 g_klauncher_self->close(); 0174 } 0175 // We don't delete the app here, that's intentional. 0176 ::_exit(255); 0177 } 0178 0179 void KLauncher::setLaunchEnv(const QString &name, const QString &value) 0180 { 0181 #ifndef USE_KPROCESS_FOR_KIOSLAVES 0182 klauncher_header request_header; 0183 QByteArray requestData; 0184 requestData.append(name.toLocal8Bit()).append('\0').append(value.toLocal8Bit()).append('\0'); 0185 request_header.cmd = LAUNCHER_SETENV; 0186 request_header.arg_length = requestData.size(); 0187 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); 0188 kde_safe_write(kdeinitSocket, requestData.data(), request_header.arg_length); 0189 #else 0190 Q_UNUSED(name); 0191 Q_UNUSED(value); 0192 #endif 0193 } 0194 0195 #ifndef USE_KPROCESS_FOR_KIOSLAVES 0196 /* 0197 * Read 'len' bytes from 'sock' into buffer. 0198 * returns -1 on failure, 0 on no data. 0199 */ 0200 static int 0201 read_socket(int sock, char *buffer, int len) 0202 { 0203 int bytes_left = len; 0204 while (bytes_left > 0) { 0205 // in case we get a request to start an application and data arrive 0206 // to kdeinitSocket at the same time, requestStart() will already 0207 // call slotKDEInitData(), so we must check there's still something 0208 // to read, otherwise this would block 0209 0210 // Same thing if kdeinit dies without warning. 0211 0212 fd_set in; 0213 timeval tm = { 30, 0 }; // 30 seconds timeout, so we're not stuck in case kdeinit dies on us 0214 FD_ZERO(&in); 0215 FD_SET(sock, &in); 0216 select(sock + 1, &in, nullptr, nullptr, &tm); 0217 if (!FD_ISSET(sock, &in)) { 0218 qCDebug(KLAUNCHER) << "read_socket" << sock << "nothing to read, kdeinit5 must be dead"; 0219 return -1; 0220 } 0221 0222 const ssize_t result = read(sock, buffer, bytes_left); 0223 if (result > 0) { 0224 buffer += result; 0225 bytes_left -= result; 0226 } else if (result == 0) { 0227 return -1; 0228 } else if ((result == -1) && (errno != EINTR)) { 0229 return -1; 0230 } 0231 } 0232 return 0; 0233 } 0234 #endif 0235 0236 void 0237 KLauncher::slotKDEInitData(int) 0238 { 0239 #ifndef USE_KPROCESS_FOR_KIOSLAVES 0240 klauncher_header request_header; 0241 QByteArray requestData; 0242 0243 int result = read_socket(kdeinitSocket, (char *) &request_header, 0244 sizeof(request_header)); 0245 if (result != -1) { 0246 requestData.resize(request_header.arg_length); 0247 result = read_socket(kdeinitSocket, (char *) requestData.data(), 0248 request_header.arg_length); 0249 } 0250 if (result == -1) { 0251 qCDebug(KLAUNCHER) << "Exiting on read_socket errno:" << errno; 0252 signal(SIGHUP, SIG_IGN); 0253 signal(SIGTERM, SIG_IGN); 0254 destruct(); // Exit! 0255 } 0256 0257 processRequestReturn(request_header.cmd, requestData); 0258 #endif 0259 } 0260 0261 void KLauncher::processRequestReturn(int status, const QByteArray &requestData) 0262 { 0263 if (status == LAUNCHER_CHILD_DIED) { 0264 long *request_data; 0265 request_data = (long *) requestData.data(); 0266 processDied(request_data[0], request_data[1]); 0267 return; 0268 } 0269 if (lastRequest && (status == LAUNCHER_OK)) { 0270 long *request_data; 0271 request_data = (long *) requestData.data(); 0272 lastRequest->pid = (pid_t)(*request_data); 0273 qCDebug(KLAUNCHER).nospace() << lastRequest->name << " (pid " << lastRequest->pid << ") up and running."; 0274 switch (lastRequest->dbus_startup_type) { 0275 case KService::DBusNone: 0276 if (lastRequest->wait) { 0277 lastRequest->status = KLaunchRequest::Launching; 0278 } else { 0279 lastRequest->status = KLaunchRequest::Running; 0280 } 0281 break; 0282 case KService::DBusUnique: 0283 case KService::DBusMulti: 0284 lastRequest->status = KLaunchRequest::Launching; 0285 break; 0286 } 0287 lastRequest = nullptr; 0288 return; 0289 } 0290 if (lastRequest && (status == LAUNCHER_ERROR)) { 0291 lastRequest->status = KLaunchRequest::Error; 0292 qCDebug(KLAUNCHER) << lastRequest->name << " failed."; 0293 if (!requestData.isEmpty()) { 0294 lastRequest->errorMsg = QString::fromUtf8((char *) requestData.data()); 0295 } 0296 lastRequest = nullptr; 0297 return; 0298 } 0299 0300 qCWarning(KLAUNCHER) << "Unexpected request return" << (unsigned int) status; 0301 } 0302 0303 void 0304 KLauncher::processDied(pid_t pid, long exitStatus) 0305 { 0306 qCDebug(KLAUNCHER) << pid << "exitStatus=" << exitStatus; 0307 for (KLaunchRequest *request : std::as_const(requestList)) { 0308 qCDebug(KLAUNCHER) << " had pending request" << request->pid; 0309 if (request->pid == pid) { 0310 if ((request->dbus_startup_type == KService::DBusUnique) 0311 && QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { 0312 request->status = KLaunchRequest::Running; 0313 qCDebug(KLAUNCHER) << pid << "running as a unique app"; 0314 } else if(request->dbus_startup_type == KService::DBusNone && request->wait) { 0315 request->status = KLaunchRequest::Running; 0316 qCDebug(KLAUNCHER) << pid << "running as DBusNone with wait to true"; 0317 } else if (exitStatus == 0 && 0318 (request->dbus_startup_type == KService::DBusUnique || 0319 request->dbus_startup_type == KService::DBusMulti)) { 0320 // e.g. opening kate from a widget on the panel/desktop, where it 0321 // shows the session chooser dialog before ever entering the main 0322 // app event loop, then quitting/closing the dialog without starting kate 0323 request->status = KLaunchRequest::Done; 0324 qCDebug(KLAUNCHER) << pid << "exited without error, requestDone. status=" << request->status; 0325 } else { 0326 request->status = KLaunchRequest::Error; 0327 qCDebug(KLAUNCHER) << pid << "died, requestDone. status=" << request->status; 0328 } 0329 requestDone(request); 0330 return; 0331 } 0332 } 0333 qCDebug(KLAUNCHER) << "found no pending requests for PID" << pid; 0334 } 0335 0336 static bool matchesPendingRequest(const QString &appId, const QString &pendingAppId) 0337 { 0338 // appId just registered, e.g. org.koffice.kword-12345 0339 // Let's see if this is what pendingAppId (e.g. org.koffice.kword or *.kword) was waiting for. 0340 0341 const QString newAppId = appId.left(appId.lastIndexOf(QLatin1Char('-'))); // strip out the -12345 if present. 0342 0343 qCDebug(KLAUNCHER) << "appId=" << appId << "newAppId=" << newAppId << "pendingAppId=" << pendingAppId; 0344 0345 if (pendingAppId.startsWith(QLatin1String("*."))) { 0346 const QString pendingName = pendingAppId.mid(2); 0347 const QString appName = newAppId.mid(newAppId.lastIndexOf(QLatin1Char('.')) + 1); 0348 qCDebug(KLAUNCHER) << "appName=" << appName; 0349 return appName == pendingName; 0350 } 0351 0352 // Match sandboxed apps (e.g. flatpak), see https://phabricator.kde.org/D5775 0353 if (newAppId.endsWith(QLatin1String(".kdbus"))) { 0354 return newAppId.leftRef(newAppId.length() - 6) == pendingAppId; 0355 } 0356 0357 return newAppId == pendingAppId; 0358 } 0359 0360 void 0361 KLauncher::slotNameOwnerChanged(const QString &appId, const QString &oldOwner, 0362 const QString &newOwner) 0363 { 0364 Q_UNUSED(oldOwner); 0365 if (appId.isEmpty() || newOwner.isEmpty()) { 0366 return; 0367 } 0368 0369 qCDebug(KLAUNCHER) << "new app" << appId; 0370 for (KLaunchRequest *request : std::as_const(requestList)) { 0371 if (request->status != KLaunchRequest::Launching) { 0372 continue; 0373 } 0374 0375 qCDebug(KLAUNCHER) << "had pending request" << request->name << s_DBusStartupTypeToString[request->dbus_startup_type] << "dbus_name" << request->dbus_name << request->tolerant_dbus_name; 0376 0377 // For unique services check the requested service name first 0378 if (request->dbus_startup_type == KService::DBusUnique) { 0379 if ((appId == request->dbus_name) || // just started 0380 QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { // was already running 0381 request->status = KLaunchRequest::Running; 0382 qCDebug(KLAUNCHER) << "OK, unique app" << request->dbus_name << "is running"; 0383 requestDone(request); 0384 continue; 0385 } else { 0386 qCDebug(KLAUNCHER) << "unique app" << request->dbus_name << "not running yet"; 0387 } 0388 } 0389 0390 const QString rAppId = !request->tolerant_dbus_name.isEmpty() ? request->tolerant_dbus_name : request->dbus_name; 0391 qCDebug(KLAUNCHER) << "using" << rAppId << "for matching"; 0392 if (rAppId.isEmpty()) { 0393 continue; 0394 } 0395 0396 if (matchesPendingRequest(appId, rAppId)) { 0397 qCDebug(KLAUNCHER) << "ok, request done"; 0398 request->dbus_name = appId; 0399 request->status = KLaunchRequest::Running; 0400 requestDone(request); 0401 continue; 0402 } 0403 } 0404 } 0405 0406 void 0407 KLauncher::requestDone(KLaunchRequest *request) 0408 { 0409 if ((request->status == KLaunchRequest::Running) || 0410 (request->status == KLaunchRequest::Done)) { 0411 requestResult.result = 0; 0412 requestResult.dbusName = request->dbus_name; 0413 requestResult.error = QStringLiteral(""); // not null, cf assert further down 0414 requestResult.pid = request->pid; 0415 } else { 0416 requestResult.result = 1; 0417 requestResult.dbusName.clear(); 0418 requestResult.error = i18n("KDEInit could not launch '%1'", request->name); 0419 if (!request->errorMsg.isEmpty()) { 0420 requestResult.error += QStringLiteral(":\n") + request->errorMsg; 0421 } 0422 requestResult.pid = 0; 0423 0424 #if HAVE_XCB 0425 if (!request->startup_dpy.isEmpty() && mIsX11) { 0426 XCBConnection conn = getXCBConnection(request->startup_dpy); 0427 if (conn) { 0428 KStartupInfoId id; 0429 id.initId(request->startup_id); 0430 KStartupInfo::sendFinishXcb(conn.conn, conn.screen, id); 0431 } 0432 } 0433 #endif 0434 } 0435 0436 if (request->transaction.type() != QDBusMessage::InvalidMessage) { 0437 if (requestResult.dbusName.isNull()) { // null strings can't be sent 0438 requestResult.dbusName.clear(); 0439 } 0440 Q_ASSERT(!requestResult.error.isNull()); 0441 quintptr stream_pid = requestResult.pid; 0442 QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << requestResult.result 0443 << requestResult.dbusName 0444 << requestResult.error 0445 << stream_pid)); 0446 } 0447 0448 qCDebug(KLAUNCHER) << "removing done request" << request->name << "PID" << request->pid; 0449 requestList.removeAll(request); 0450 delete request; 0451 } 0452 0453 static void appendLong(QByteArray &ba, long l) 0454 { 0455 const int sz = ba.size(); 0456 ba.resize(sz + sizeof(long)); 0457 memcpy(ba.data() + sz, &l, sizeof(long)); 0458 } 0459 0460 void 0461 KLauncher::requestStart(KLaunchRequest *request) 0462 { 0463 #ifdef USE_KPROCESS_FOR_KIOSLAVES 0464 requestList.append(request); 0465 lastRequest = request; 0466 0467 QProcess *process = new QProcess; 0468 process->setProcessChannelMode(QProcess::MergedChannels); 0469 connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(slotGotOutput())); 0470 connect(process, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotFinished(int,QProcess::ExitStatus))); 0471 request->process = process; 0472 0473 QProcessEnvironment environment = QProcessEnvironment::systemEnvironment(); 0474 for (const QString &env : std::as_const(request->envs)) { 0475 const int pos = env.indexOf(QLatin1Char('=')); 0476 const QString envVariable = env.left(pos); 0477 const QString envValue = env.mid(pos + 1); 0478 environment.insert(envVariable, envValue); 0479 } 0480 process->setProcessEnvironment(environment); 0481 0482 QStringList args; 0483 for (const QString &arg : std::as_const(request->arg_list)) { 0484 args << arg; 0485 } 0486 0487 const QString executable = QStandardPaths::findExecutable(request->name); 0488 if (executable.isEmpty()) { 0489 qCDebug(KLAUNCHER) << "KLauncher couldn't find" << request->name << "executable."; 0490 return; 0491 } 0492 0493 process->start(executable, args); 0494 0495 if (!process->waitForStarted()) { 0496 processRequestReturn(LAUNCHER_ERROR, ""); 0497 } else { 0498 request->pid = process->processId(); 0499 QByteArray data((char *)&request->pid, sizeof(int)); 0500 processRequestReturn(LAUNCHER_OK, data); 0501 } 0502 return; 0503 0504 #else 0505 requestList.append(request); 0506 // Send request to kdeinit. 0507 klauncher_header request_header; 0508 QByteArray requestData; 0509 requestData.reserve(1024); 0510 0511 appendLong(requestData, request->arg_list.count() + 1); 0512 requestData.append(request->name.toLocal8Bit()); 0513 requestData.append('\0'); 0514 for (const QString &arg : std::as_const(request->arg_list)) { 0515 requestData.append(arg.toLocal8Bit()).append('\0'); 0516 } 0517 appendLong(requestData, request->envs.count()); 0518 for (const QString &env : std::as_const(request->envs)) { 0519 requestData.append(env.toLocal8Bit()).append('\0'); 0520 } 0521 appendLong(requestData, 0); // avoid_loops, always false here 0522 #if HAVE_X11 0523 bool startup_notify = mIsX11 && !request->startup_id.isNull() && request->startup_id != "0"; 0524 if (startup_notify) { 0525 requestData.append(request->startup_id).append('\0'); 0526 } 0527 #endif 0528 if (!request->cwd.isEmpty()) { 0529 requestData.append(QFile::encodeName(request->cwd)).append('\0'); 0530 } 0531 0532 #if HAVE_X11 0533 request_header.cmd = startup_notify ? LAUNCHER_EXT_EXEC : LAUNCHER_EXEC_NEW; 0534 #else 0535 request_header.cmd = LAUNCHER_EXEC_NEW; 0536 #endif 0537 request_header.arg_length = requestData.length(); 0538 0539 qCDebug(KLAUNCHER) << "Asking kdeinit to start" << request->name << request->arg_list 0540 << "cmd=" << commandToString(request_header.cmd); 0541 0542 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); 0543 kde_safe_write(kdeinitSocket, requestData.data(), requestData.length()); 0544 0545 // Wait for pid to return. 0546 lastRequest = request; 0547 do { 0548 slotKDEInitData(kdeinitSocket); 0549 } while (lastRequest != nullptr); 0550 #endif 0551 } 0552 0553 void KLauncher::exec_blind(const QString &name, const QStringList &arg_list, const QStringList &envs, const QString &startup_id) 0554 { 0555 KLaunchRequest *request = new KLaunchRequest; 0556 request->name = name; 0557 request->arg_list = arg_list; 0558 request->dbus_startup_type = KService::DBusNone; 0559 request->pid = 0; 0560 request->status = KLaunchRequest::Launching; 0561 request->envs = envs; 0562 request->wait = false; 0563 // Find service, if any - strip path if needed 0564 KService::Ptr service = KService::serviceByDesktopName(name.mid(name.lastIndexOf(QLatin1Char('/')) + 1)); 0565 if (service) { 0566 send_service_startup_info(request, service, startup_id.toLocal8Bit(), QStringList()); 0567 } else { // no .desktop file, no startup info 0568 cancel_service_startup_info(request, startup_id.toLocal8Bit(), envs); 0569 } 0570 0571 requestStart(request); 0572 // We don't care about this request any longer.... 0573 requestDone(request); 0574 } 0575 0576 bool 0577 KLauncher::start_service_by_desktop_path(const QString &serviceName, const QStringList &urls, 0578 const QStringList &envs, const QString &startup_id, bool blind, const QDBusMessage &msg) 0579 { 0580 KService::Ptr service; 0581 // Find service 0582 const QFileInfo fi(serviceName); 0583 if (fi.isAbsolute() && fi.exists()) { 0584 // Full path 0585 service = new KService(serviceName); 0586 } else { 0587 service = KService::serviceByDesktopPath(serviceName); 0588 // TODO? 0589 //if (!service) 0590 // service = KService::serviceByStorageId(serviceName); // This method should be named start_service_by_storage_id ideally... 0591 } 0592 if (!service) { 0593 requestResult.result = ENOENT; 0594 requestResult.error = i18n("Could not find service '%1'.", serviceName); 0595 cancel_service_startup_info(nullptr, startup_id.toLocal8Bit(), envs); // cancel it if any 0596 return false; 0597 } 0598 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, msg); 0599 } 0600 0601 bool 0602 KLauncher::start_service_by_desktop_name(const QString &serviceName, const QStringList &urls, 0603 const QStringList &envs, const QString &startup_id, bool blind, const QDBusMessage &msg) 0604 { 0605 KService::Ptr service = KService::serviceByDesktopName(serviceName); 0606 if (!service) { 0607 requestResult.result = ENOENT; 0608 requestResult.error = i18n("Could not find service '%1'.", serviceName); 0609 cancel_service_startup_info(nullptr, startup_id.toLocal8Bit(), envs); // cancel it if any 0610 return false; 0611 } 0612 return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, msg); 0613 } 0614 0615 bool 0616 KLauncher::start_service(KService::Ptr service, const QStringList &_urls, 0617 const QStringList &envs, const QByteArray &startup_id, 0618 bool blind, const QDBusMessage &msg) 0619 { 0620 QStringList urls = _urls; 0621 bool runPermitted = KDesktopFile::isAuthorizedDesktopFile(service->entryPath()); 0622 0623 if (!service->isValid() || !runPermitted) { 0624 requestResult.result = ENOEXEC; 0625 if (service->isValid()) { 0626 requestResult.error = i18n("Service '%1' must be executable to run.", service->entryPath()); 0627 } else { 0628 requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath()); 0629 } 0630 cancel_service_startup_info(nullptr, startup_id, envs); // cancel it if any 0631 return false; 0632 } 0633 KLaunchRequest *request = new KLaunchRequest; 0634 0635 enum DiscreteGpuCheck { NotChecked, Present, Absent }; 0636 static DiscreteGpuCheck s_gpuCheck = NotChecked; 0637 0638 if (service->runOnDiscreteGpu() && s_gpuCheck == NotChecked) { 0639 // Check whether we have a discrete gpu 0640 bool hasDiscreteGpu = false; 0641 QDBusInterface iface(QLatin1String("org.kde.Solid.PowerManagement"), 0642 QLatin1String("/org/kde/Solid/PowerManagement"), 0643 QLatin1String("org.kde.Solid.PowerManagement"), 0644 QDBusConnection::sessionBus()); 0645 if (iface.isValid()) { 0646 QDBusReply<bool> reply = iface.call(QLatin1String("hasDualGpu")); 0647 if (reply.isValid()) { 0648 hasDiscreteGpu = reply.value(); 0649 } 0650 } 0651 0652 s_gpuCheck = hasDiscreteGpu ? Present : Absent; 0653 } 0654 0655 QStringList _envs = envs; 0656 if (service->runOnDiscreteGpu() && s_gpuCheck == Present) { 0657 _envs << QLatin1String("DRI_PRIME=1"); 0658 } 0659 0660 if ((urls.count() > 1) && !service->allowMultipleFiles()) { 0661 // We need to launch the application N times. That sucks. 0662 // We ignore the result for application 2 to N. 0663 // For the first file we launch the application in the 0664 // usual way. The reported result is based on this 0665 // application. 0666 QStringList::ConstIterator it = urls.constBegin(); 0667 for (++it; 0668 it != urls.constEnd(); 0669 ++it) { 0670 QStringList singleUrl; 0671 singleUrl.append(*it); 0672 QByteArray startup_id2 = startup_id; 0673 if (!startup_id2.isEmpty() && startup_id2 != "0") { 0674 startup_id2 = "0"; // can't use the same startup_id several times // krazy:exclude=doublequote_chars 0675 } 0676 start_service(service, singleUrl, _envs, startup_id2, true, msg); 0677 } 0678 const QString firstURL = urls.at(0); 0679 urls.clear(); 0680 urls.append(firstURL); 0681 } 0682 const QList<QUrl> qurls = QUrl::fromStringList(urls); 0683 0684 createArgs(request, service, qurls); 0685 0686 // We must have one argument at least! 0687 if (request->arg_list.isEmpty()) { 0688 requestResult.result = ENOEXEC; 0689 requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath()); 0690 delete request; 0691 cancel_service_startup_info(nullptr, startup_id, _envs); 0692 return false; 0693 } 0694 0695 request->name = request->arg_list.takeFirst(); 0696 0697 if (request->name.endsWith(QLatin1String("/kioexec"))) { 0698 // Special case for kioexec; if createArgs said we were going to use it, 0699 // then we have to expect a kioexec-PID, not a org.kde.finalapp... 0700 // Testcase: konqueror www.kde.org, RMB on link, open with, kruler. 0701 0702 request->dbus_startup_type = KService::DBusMulti; 0703 request->dbus_name = QStringLiteral("org.kde.kioexec"); 0704 } else { 0705 request->dbus_startup_type = service->dbusStartupType(); 0706 0707 if ((request->dbus_startup_type == KService::DBusUnique) || 0708 (request->dbus_startup_type == KService::DBusMulti)) { 0709 const QVariant v = service->property(QStringLiteral("X-DBUS-ServiceName")); 0710 if (v.isValid()) { 0711 request->dbus_name = v.toString(); 0712 } 0713 if (request->dbus_name.isEmpty()) { 0714 const QString binName = KIO::DesktopExecParser::executableName(service->exec()); 0715 request->dbus_name = QStringLiteral("org.kde.") + binName; 0716 request->tolerant_dbus_name = QStringLiteral("*.") + binName; 0717 } 0718 } 0719 } 0720 0721 qCDebug(KLAUNCHER) << "name=" << request->name << "dbus_name=" << request->dbus_name 0722 << "startup type=" << s_DBusStartupTypeToString[request->dbus_startup_type]; 0723 0724 request->pid = 0; 0725 request->wait = false; 0726 request->envs = _envs; 0727 send_service_startup_info(request, service, startup_id, _envs); 0728 0729 // Request will be handled later. 0730 if (!blind) { 0731 msg.setDelayedReply(true); 0732 request->transaction = msg; 0733 } 0734 queueRequest(request); 0735 return true; 0736 } 0737 0738 0739 namespace KIOGuiPrivate { 0740 // defined in kprocessrunner.cpp 0741 extern bool KIOGUI_EXPORT checkStartupNotify(const KService *service, bool *silent_arg, 0742 QByteArray *wmclass_arg); 0743 } 0744 0745 void 0746 KLauncher::send_service_startup_info(KLaunchRequest *request, KService::Ptr service, const QByteArray &startup_id, 0747 const QStringList &envs) 0748 { 0749 #if HAVE_XCB 0750 if (!mIsX11) { 0751 return; 0752 } 0753 request->startup_id = "0";// krazy:exclude=doublequote_chars 0754 if (startup_id == "0") { 0755 return; 0756 } 0757 bool silent; 0758 QByteArray wmclass; 0759 0760 if (!KIOGuiPrivate::checkStartupNotify(service.data(), &silent, &wmclass)) { 0761 return; 0762 } 0763 KStartupInfoId id; 0764 id.initId(startup_id); 0765 QByteArray dpy_str; 0766 for (const QString &env : envs) { 0767 if (env.startsWith(QLatin1String("DISPLAY="))) { 0768 dpy_str = env.mid(8).toLocal8Bit(); 0769 } 0770 } 0771 0772 XCBConnection conn = getXCBConnection(dpy_str); 0773 request->startup_id = id.id(); 0774 if (!conn) { 0775 cancel_service_startup_info(request, startup_id, envs); 0776 return; 0777 } 0778 0779 request->startup_dpy = conn.displayName; 0780 0781 KStartupInfoData data; 0782 data.setName(service->name()); 0783 data.setIcon(service->icon()); 0784 data.setDescription(i18n("Launching %1", service->name())); 0785 if (!wmclass.isEmpty()) { 0786 data.setWMClass(wmclass); 0787 } 0788 if (silent) { 0789 data.setSilent(KStartupInfoData::Yes); 0790 } 0791 data.setApplicationId(service->entryPath()); 0792 // the rest will be sent by kdeinit 0793 KStartupInfo::sendStartupXcb(conn.conn, conn.screen, id, data); 0794 #endif 0795 } 0796 0797 void 0798 KLauncher::cancel_service_startup_info(KLaunchRequest *request, const QByteArray &startup_id, 0799 const QStringList &envs) 0800 { 0801 #if HAVE_XCB 0802 if (request != nullptr) { 0803 request->startup_id = "0"; // krazy:exclude=doublequote_chars 0804 } 0805 if (!startup_id.isEmpty() && startup_id != "0" && mIsX11) { 0806 QString dpy_str; 0807 for (const QString &env : envs) { 0808 if (env.startsWith(QLatin1String("DISPLAY="))) { 0809 dpy_str = env.mid(8); 0810 } 0811 } 0812 XCBConnection conn = getXCBConnection(dpy_str.toLocal8Bit()); 0813 if (!conn) { 0814 return; 0815 } 0816 KStartupInfoId id; 0817 id.initId(startup_id); 0818 KStartupInfo::sendFinishXcb(conn.conn, conn.screen, id); 0819 } 0820 #endif 0821 } 0822 0823 bool 0824 KLauncher::kdeinit_exec(const QString &app, const QStringList &args, 0825 const QString &workdir, const QStringList &envs, 0826 const QString &startup_id, bool wait, const QDBusMessage &msg) 0827 { 0828 KLaunchRequest *request = new KLaunchRequest; 0829 request->arg_list = args; 0830 request->name = app; 0831 request->dbus_startup_type = KService::DBusNone; 0832 request->pid = 0; 0833 request->wait = wait; 0834 #if HAVE_X11 0835 request->startup_id = startup_id.toLocal8Bit(); 0836 #endif 0837 request->envs = envs; 0838 request->cwd = workdir; 0839 #if HAVE_X11 0840 if (!app.endsWith(QLatin1String("kbuildsycoca5"))) { // avoid stupid loop 0841 // Find service, if any - strip path if needed 0842 const QString desktopName = app.mid(app.lastIndexOf(QLatin1Char('/')) + 1); 0843 KService::Ptr service = KService::serviceByDesktopName(desktopName); 0844 if (service) 0845 send_service_startup_info(request, service, 0846 request->startup_id, envs); 0847 else { // no .desktop file, no startup info 0848 cancel_service_startup_info(request, request->startup_id, envs); 0849 } 0850 } 0851 #endif 0852 msg.setDelayedReply(true); 0853 request->transaction = msg; 0854 queueRequest(request); 0855 return true; 0856 } 0857 0858 void 0859 KLauncher::queueRequest(KLaunchRequest *request) 0860 { 0861 requestQueue.append(request); 0862 if (!bProcessingQueue) { 0863 bProcessingQueue = true; 0864 QTimer::singleShot(0, this, SLOT(slotDequeue())); 0865 } 0866 } 0867 0868 void 0869 KLauncher::slotDequeue() 0870 { 0871 do { 0872 KLaunchRequest *request = requestQueue.takeFirst(); 0873 // process request 0874 request->status = KLaunchRequest::Launching; 0875 requestStart(request); 0876 if (request->status != KLaunchRequest::Launching) { 0877 // Request handled. 0878 qCDebug(KLAUNCHER) << "Request handled already"; 0879 requestDone(request); 0880 continue; 0881 } 0882 } while (!requestQueue.isEmpty()); 0883 bProcessingQueue = false; 0884 } 0885 0886 void 0887 KLauncher::createArgs(KLaunchRequest *request, const KService::Ptr service, 0888 const QList<QUrl> &urls) 0889 { 0890 KIO::DesktopExecParser parser(*service, urls); 0891 const QStringList params = parser.resultingArguments(); 0892 for (const QString &arg : params) { 0893 request->arg_list.append(arg); 0894 } 0895 0896 const QString& path = service->workingDirectory(); 0897 if (!path.isEmpty()) { 0898 request->cwd = path; 0899 } else if (!urls.isEmpty()) { 0900 const QUrl& url = urls.first(); 0901 if (url.isLocalFile()) { 0902 request->cwd = url.adjusted(QUrl::RemoveFilename).toLocalFile(); 0903 } 0904 } 0905 } 0906 0907 ///// IO-Slave functions 0908 0909 pid_t 0910 KLauncher::requestHoldSlave(const QString &urlStr, const QString &app_socket) 0911 { 0912 const QUrl url(urlStr); 0913 IdleSlave *slave = nullptr; 0914 for (IdleSlave *p : std::as_const(mSlaveList)) { 0915 if (p->onHold(url)) { 0916 slave = p; 0917 break; 0918 } 0919 } 0920 if (slave) { 0921 mSlaveList.removeAll(slave); 0922 slave->connect(app_socket); 0923 return slave->pid(); 0924 } 0925 return 0; 0926 } 0927 0928 pid_t 0929 KLauncher::requestSlave(const QString &protocol, 0930 const QString &host, 0931 const QString &app_socket, 0932 QString &error) 0933 { 0934 IdleSlave *slave = nullptr; 0935 for (IdleSlave *p : std::as_const(mSlaveList)) { 0936 if (p->match(protocol, host, true)) { 0937 slave = p; 0938 break; 0939 } 0940 } 0941 if (!slave) { 0942 for (IdleSlave *p : std::as_const(mSlaveList)) { 0943 if (p->match(protocol, host, false)) { 0944 slave = p; 0945 break; 0946 } 0947 } 0948 } 0949 if (!slave) { 0950 for (IdleSlave *p : std::as_const(mSlaveList)) { 0951 if (p->match(protocol, QString(), false)) { 0952 slave = p; 0953 break; 0954 } 0955 } 0956 } 0957 if (slave) { 0958 mSlaveList.removeAll(slave); 0959 slave->connect(app_socket); 0960 return slave->pid(); 0961 } 0962 0963 QString slaveModule = KProtocolInfo::exec(protocol); 0964 if (slaveModule.isEmpty()) { 0965 error = i18n("Unknown protocol '%1'.\n", protocol); 0966 return 0; 0967 } 0968 KPluginLoader loader(slaveModule); 0969 QString slaveModulePath = loader.fileName(); 0970 if (slaveModulePath.isEmpty()) { 0971 error = i18n("Could not find the '%1' plugin.\n", slaveModule); 0972 return 0; 0973 } 0974 0975 QStringList arg_list; 0976 #ifdef USE_KPROCESS_FOR_KIOSLAVES 0977 arg_list << slaveModulePath; 0978 arg_list << protocol; 0979 arg_list << mConnectionServer.address().toString(); 0980 arg_list << app_socket; 0981 #ifdef Q_OS_WIN 0982 QString name = QLatin1String("kioslave"); 0983 #else 0984 QString name = QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioslave"); 0985 #endif 0986 #else 0987 QString arg1 = protocol; 0988 QString arg2 = mConnectionServer.address().toString(); 0989 QString arg3 = app_socket; 0990 arg_list.append(arg1); 0991 arg_list.append(arg2); 0992 arg_list.append(arg3); 0993 QString name = slaveModulePath; 0994 #endif 0995 0996 qCDebug(KLAUNCHER) << "KLauncher: launching new slave" << name << "with protocol=" << protocol << "args=" << arg_list; 0997 0998 #ifdef Q_OS_UNIX 0999 #ifndef USE_KPROCESS_FOR_KIOSLAVES 1000 // see comments where mSlaveDebug is set in KLauncher::KLauncher 1001 if (mSlaveDebug == protocol) { 1002 klauncher_header request_header; 1003 request_header.cmd = LAUNCHER_DEBUG_WAIT; 1004 request_header.arg_length = 0; 1005 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); 1006 } 1007 #endif 1008 if (mSlaveValgrind == protocol) { 1009 arg_list.prepend(name); 1010 #ifndef USE_KPROCESS_FOR_KIOSLAVES // otherwise we've already done this 1011 #ifdef Q_OS_WIN 1012 arg_list.prepend(QLatin1String("kioslave")); 1013 #else 1014 arg_list.prepend(QFile::decodeName(KDE_INSTALL_FULL_LIBEXECDIR_KF5 "/kioslave")); 1015 #endif 1016 #endif 1017 name = QStringLiteral("valgrind"); 1018 1019 if (!mSlaveValgrindSkin.isEmpty()) { 1020 arg_list.prepend(QLatin1String("--tool=") + mSlaveValgrindSkin); 1021 } else { 1022 arg_list.prepend(QLatin1String("--tool=memcheck")); 1023 } 1024 } 1025 #endif 1026 KLaunchRequest *request = new KLaunchRequest; 1027 request->name = name; 1028 request->arg_list = arg_list; 1029 request->dbus_startup_type = KService::DBusNone; 1030 request->pid = 0; 1031 request->wait = false; 1032 #if HAVE_X11 1033 request->startup_id = "0"; // krazy:exclude=doublequote_chars 1034 #endif 1035 request->status = KLaunchRequest::Launching; 1036 requestStart(request); 1037 pid_t pid = request->pid; 1038 1039 // qCDebug(KLAUNCHER) << "Slave launched, pid = " << pid; 1040 1041 // We don't care about this request any longer.... 1042 requestDone(request); 1043 if (!pid) { 1044 error = i18n("Error loading '%1'.", name); 1045 } 1046 return pid; 1047 } 1048 1049 bool KLauncher::checkForHeldSlave(const QString &urlStr) 1050 { 1051 QUrl url(urlStr); 1052 for (const IdleSlave *p : std::as_const(mSlaveList)) { 1053 if (p->onHold(url)) { 1054 return true; 1055 } 1056 } 1057 return false; 1058 } 1059 1060 void 1061 KLauncher::waitForSlave(int pid) 1062 { 1063 Q_ASSERT(calledFromDBus()); 1064 for (IdleSlave *slave : std::as_const(mSlaveList)) { 1065 if (slave->pid() == static_cast<pid_t>(pid)) { 1066 return; // Already here. 1067 } 1068 } 1069 SlaveWaitRequest *waitRequest = new SlaveWaitRequest; 1070 setDelayedReply(true); 1071 waitRequest->transaction = message(); // from QDBusContext 1072 waitRequest->pid = static_cast<pid_t>(pid); 1073 mSlaveWaitRequest.append(waitRequest); 1074 } 1075 1076 void 1077 KLauncher::acceptSlave() 1078 { 1079 IdleSlave *slave = new IdleSlave(this); 1080 mConnectionServer.setNextPendingConnection(slave->connection()); 1081 mSlaveList.append(slave); 1082 connect(slave, SIGNAL(destroyed()), this, SLOT(slotSlaveGone())); 1083 connect(slave, SIGNAL(statusUpdate(IdleSlave*)), 1084 this, SLOT(slotSlaveStatus(IdleSlave*))); 1085 if (!mTimer.isActive()) { 1086 mTimer.start(1000 * 10); 1087 } 1088 } 1089 1090 void 1091 KLauncher::slotSlaveStatus(IdleSlave *slave) 1092 { 1093 QMutableListIterator<SlaveWaitRequest *> it(mSlaveWaitRequest); 1094 while (it.hasNext()) { 1095 SlaveWaitRequest *waitRequest = it.next(); 1096 if (waitRequest->pid == slave->pid()) { 1097 QDBusConnection::sessionBus().send(waitRequest->transaction.createReply()); 1098 it.remove(); 1099 delete waitRequest; 1100 } 1101 } 1102 1103 if (slave->hasTempAuthorization()) { 1104 mSlaveList.removeAll(slave); 1105 slave->deleteLater(); 1106 } 1107 } 1108 1109 void 1110 KLauncher::slotSlaveGone() 1111 { 1112 IdleSlave *slave = (IdleSlave *) sender(); 1113 mSlaveList.removeAll(slave); 1114 if ((mSlaveList.isEmpty()) && (mTimer.isActive())) { 1115 mTimer.stop(); 1116 } 1117 } 1118 1119 void 1120 KLauncher::idleTimeout() 1121 { 1122 bool keepOneFileSlave = true; 1123 QDateTime now = QDateTime::currentDateTime(); 1124 for (IdleSlave *slave : std::as_const(mSlaveList)) { 1125 if ((slave->protocol() == QLatin1String("file")) && (keepOneFileSlave)) { 1126 keepOneFileSlave = false; 1127 } else if (slave->age(now) > SLAVE_MAX_IDLE) { 1128 // killing idle slave 1129 slave->deleteLater(); 1130 } 1131 } 1132 } 1133 1134 void KLauncher::reparseConfiguration() 1135 { 1136 KProtocolManager::reparseConfiguration(); 1137 for (IdleSlave *slave : std::as_const(mSlaveList)) { 1138 slave->reparseConfiguration(); 1139 } 1140 } 1141 1142 void 1143 KLauncher::slotGotOutput() 1144 { 1145 #ifdef USE_KPROCESS_FOR_KIOSLAVES 1146 QProcess *p = static_cast<QProcess *>(sender()); 1147 QByteArray _stdout = p->readAllStandardOutput(); 1148 qCDebug(KLAUNCHER) << _stdout.data(); 1149 #endif 1150 } 1151 1152 void 1153 KLauncher::slotFinished(int exitCode, QProcess::ExitStatus exitStatus) 1154 { 1155 #ifdef USE_KPROCESS_FOR_KIOSLAVES 1156 QProcess *p = static_cast<QProcess *>(sender()); 1157 qCDebug(KLAUNCHER) << "process finished exitcode=" << exitCode << "exitStatus=" << exitStatus; 1158 1159 for (KLaunchRequest *request : std::as_const(requestList)) { 1160 if (request->process == p) { 1161 qCDebug(KLAUNCHER) << "found KProcess, request done"; 1162 if (exitCode == 0 && exitStatus == QProcess::NormalExit) { 1163 request->status = KLaunchRequest::Done; 1164 } else { 1165 request->status = KLaunchRequest::Error; 1166 } 1167 requestDone(request); 1168 request->process = 0; 1169 } 1170 } 1171 delete p; 1172 #else 1173 Q_UNUSED(exitCode); 1174 Q_UNUSED(exitStatus); 1175 #endif 1176 } 1177 1178 void KLauncher::terminate_kdeinit() 1179 { 1180 qCDebug(KLAUNCHER); 1181 #ifndef USE_KPROCESS_FOR_KIOSLAVES 1182 klauncher_header request_header; 1183 request_header.cmd = LAUNCHER_TERMINATE_KDEINIT; 1184 request_header.arg_length = 0; 1185 kde_safe_write(kdeinitSocket, &request_header, sizeof(request_header)); 1186 #endif 1187 } 1188 1189 #if HAVE_XCB 1190 KLauncher::XCBConnection KLauncher::getXCBConnection(const QByteArray &_displayName) 1191 { 1192 const auto displayName = !_displayName.isEmpty() ? _displayName : qgetenv("DISPLAY"); 1193 1194 // If cached connection is same as request 1195 if (mCached && displayName == mCached.displayName) { 1196 // Check error, if so close it, otherwise it's still valid, reuse the cached one 1197 if (xcb_connection_has_error(mCached.conn)) { 1198 close(); 1199 } else { 1200 return mCached; 1201 } 1202 } 1203 1204 // At this point, the cached connection can't be reused, make a new connection 1205 XCBConnection conn; 1206 conn.conn = xcb_connect(displayName.constData(), &conn.screen); 1207 if (conn) { 1208 // check error first, if so return an empty one 1209 if (xcb_connection_has_error(conn.conn)) { 1210 xcb_disconnect(conn.conn); 1211 return XCBConnection(); 1212 } 1213 1214 // if it's valid, replace mCached with new connection 1215 conn.displayName = displayName; 1216 close(); 1217 mCached = conn; 1218 } 1219 return conn; 1220 } 1221 #endif 1222 1223 #include "moc_klauncher.cpp"