/* * Copyright (C) 2013 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "shellmanager.h" #include #include #include #include #include #include #include #include #include //#include #include "shellcorona.h" #include "config-workspace.h" #include static const QString s_shellsDir(QStandardPaths::locate(QStandardPaths::QStandardPaths::GenericDataLocation, PLASMA_RELATIVE_DATA_INSTALL_DIR "/shells/", QStandardPaths::LocateDirectory)); static const QString s_shellLoaderPath = QStringLiteral("/contents/loader.qml"); bool ShellManager::s_forceWindowed = false; bool ShellManager::s_noRespawn = false; bool ShellManager::s_standaloneOption = false; int ShellManager::s_crashes = 0; QString ShellManager::s_fixedShell; QString ShellManager::s_restartOptions; // // ShellManager // class ShellManager::Private { public: Private() : currentHandler(nullptr), corona(0) { shellUpdateDelay.setInterval(100); shellUpdateDelay.setSingleShot(true); } QList handlers; QObject * currentHandler; QTimer shellUpdateDelay; ShellCorona * corona; }; ShellManager::ShellManager() : d(new Private()) { // Using setCrashHandler, we end up in an infinite loop instead of quitting, // use setEmergencySaveFunction instead to avoid this. KCrash::setEmergencySaveFunction(ShellManager::crashHandler); QTimer::singleShot(15 * 1000, this, SLOT(resetCrashCount())); connect( &d->shellUpdateDelay, &QTimer::timeout, this, &ShellManager::updateShell ); //we have to ensure this is executed after QCoreApplication::exec() QMetaObject::invokeMethod(this, "loadHandlers", Qt::QueuedConnection); } ShellManager::~ShellManager() { // if (d->currentHandler) // d->currentHandler->unload(); } void ShellManager::loadHandlers() { //this should be executed one single time in the app life cycle Q_ASSERT(!d->corona); d->corona = new ShellCorona(this); connect( this, &ShellManager::shellChanged, d->corona, &ShellCorona::setShell ); // TODO: Use corona's qml engine when it switches from QScriptEngine static QQmlEngine * engine = new QQmlEngine(this); for (const auto & dir: QDir(s_shellsDir).entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { const QString qmlFile = s_shellsDir + dir + s_shellLoaderPath; // qDebug() << "Making a new instance of " << qmlFile; QQmlComponent handlerComponent(engine, QUrl::fromLocalFile(qmlFile) ); auto handler = handlerComponent.create(); // Writing out the errors for (const auto & error: handlerComponent.errors()) { qWarning() << "Error: " << error; } if (handler) { handler->setProperty("pluginName", dir); registerHandler(handler); } } updateShell(); } void ShellManager::registerHandler(QObject * handler) { // qDebug() << "We got the handler: " << handler->property("shell").toString(); connect( handler, &QObject::destroyed, this, &ShellManager::deregisterHandler ); connect( handler, SIGNAL(willingChanged()), this, SLOT(requestShellUpdate()) ); connect( handler, SIGNAL(priorityChanged()), this, SLOT(requestShellUpdate()) ); d->handlers.push_back(handler); } void ShellManager::deregisterHandler(QObject * handler) { if (d->handlers.contains(handler)) { d->handlers.removeAll(handler); handler->disconnect(this); } if (d->currentHandler == handler) { d->currentHandler = nullptr; updateShell(); } handler->deleteLater(); } void ShellManager::requestShellUpdate() { d->shellUpdateDelay.start(); } void ShellManager::updateShell() { d->shellUpdateDelay.stop(); if (d->handlers.isEmpty()) { qFatal("We have no shell handlers installed"); return; } QObject *handler = 0; if (!s_fixedShell.isEmpty()) { QList::const_iterator it = std::find_if (d->handlers.cbegin(), d->handlers.cend(), [=] (QObject *handler) { return handler->property("pluginName").toString() == s_fixedShell; }); if (it != d->handlers.cend()) { handler = *it; } else { qCritical("Unable to find the shell plugin '%s'", qPrintable(s_fixedShell)); QCoreApplication::exit(); } } else { // Finding the handler that has the priority closest to zero. // We will return a handler even if there are no willing ones. handler =* std::min_element(d->handlers.cbegin(), d->handlers.cend(), [] (QObject * left, QObject * right) { auto willing = [] (QObject * handler) { return handler->property("willing").toBool(); }; auto priority = [] (QObject * handler) { return handler->property("priority").toInt(); }; return // If one is willing and the other is not, // return it - it has the priority willing(left) && !willing(right) ? true : !willing(left) && willing(right) ? false : // otherwise just compare the priorities priority(left) < priority(right); } ); } if (handler == d->currentHandler) return; // Activating the new handler and killing the old one if (d->currentHandler) { d->currentHandler->setProperty("loaded", false); } d->currentHandler = handler; d->currentHandler->setProperty("loaded", true); emit shellChanged(d->currentHandler->property("shell").toString()); } ShellManager * ShellManager::instance() { static ShellManager* manager = nullptr; if (!manager) { manager = new ShellManager; } return manager; } void ShellManager::resetCrashCount() { s_crashes = 0; } void ShellManager::crashHandler(int signal) { /* plasma-shell restart logic as crash recovery * * We restart plasma-shell after crashes. When it crashes subsequently on startup, * and more than two times in a row, we give up in order to not endlessly loop. * Once the shell process stays alive for at least 15 seconds, we reset the crash * counter, so a later crash, at runtime rather than on startup will still be * recovered from. * * This logic is very similar as to how kwin handles it. */ s_crashes++; fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, s_crashes); char cmd[1024]; sprintf(cmd, "%s %s --crashes %d &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), s_restartOptions.toLocal8Bit().constData(), s_crashes); printf("%s\n", cmd); if (s_crashes < 3 && !s_noRespawn) { sleep(1); system(cmd); } else { fprintf(stderr, "Too many crashes in short order or respawning disabled, not restarting automatically.\n"); } }