/* BEGIN_COMMON_COPYRIGHT_HEADER * (c)LGPL2+ * * LXQt - a lightweight, Qt based, desktop toolset * http://razor-qt.org, http://lxde.org/ * * Copyright: 2010-2011 LXQt team * Authors: * Petr Vanek * Hong Jen Yee (PCMan) * * This program or library is free software; you can redistribute it * and/or modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA * * END_COMMON_COPYRIGHT_HEADER */ #include "lxqtmodman.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "wmselectdialog.h" #include "windowmanager.h" #include #include "log.h" #include #include #include #define MAX_CRASHES_PER_APP 5 using namespace LXQt; /** * @brief the constructor, needs a valid modules.conf */ LXQtModuleManager::LXQtModuleManager(const QString & windowManager, QObject* parent) : QObject(parent), mWindowManager(windowManager), mWmProcess(new QProcess(this)), mThemeWatcher(new QFileSystemWatcher(this)), mWmStarted(false), mTrayStarted(false), mWaitLoop(NULL) { connect(mThemeWatcher, SIGNAL(directoryChanged(QString)), SLOT(themeFolderChanged(QString))); connect(LXQt::Settings::globalSettings(), SIGNAL(lxqtThemeChanged()), SLOT(themeChanged())); qApp->installNativeEventFilter(this); } void LXQtModuleManager::startup(LXQt::Settings& s) { // The lxqt-confupdate can update the settings of the WM, so run it first. startConfUpdate(); // Start window manager startWm(&s); startAutostartApps(); QStringList paths; paths << XdgDirs::dataHome(false); paths << XdgDirs::dataDirs(); foreach(const QString &path, paths) { QFileInfo fi(QString("%1/lxqt/themes").arg(path)); if (fi.exists()) mThemeWatcher->addPath(fi.absoluteFilePath()); } themeChanged(); } void LXQtModuleManager::startAutostartApps() { // XDG autostart XdgDesktopFileList fileList = XdgAutoStart::desktopFileList(); QList trayApps; for (XdgDesktopFileList::iterator i = fileList.begin(); i != fileList.end(); ++i) { if (i->value("X-LXQt-Need-Tray", false).toBool()) trayApps.append(&(*i)); else { startProcess(*i); qCDebug(SESSION) << "start" << i->fileName(); } } if (!trayApps.isEmpty()) { mTrayStarted = QSystemTrayIcon::isSystemTrayAvailable(); if(!mTrayStarted) { QEventLoop waitLoop; mWaitLoop = &waitLoop; // add a timeout to avoid infinite blocking if a WM fail to execute. QTimer::singleShot(60 * 1000, &waitLoop, SLOT(quit())); waitLoop.exec(); mWaitLoop = NULL; } foreach (XdgDesktopFile* f, trayApps) { qCDebug(SESSION) << "start tray app" << f->fileName(); startProcess(*f); } } } void LXQtModuleManager::themeFolderChanged(const QString& /*path*/) { QString newTheme; if (!QFileInfo(mCurrentThemePath).exists()) { const QList &allThemes = lxqtTheme.allThemes(); if (!allThemes.isEmpty()) newTheme = allThemes[0].name(); else return; } else newTheme = lxqtTheme.currentTheme().name(); LXQt::Settings settings("lxqt"); if (newTheme == settings.value("theme")) { // force the same theme to be updated settings.setValue("__theme_updated__", QDateTime::currentMSecsSinceEpoch()); } else settings.setValue("theme", newTheme); sync(); } void LXQtModuleManager::themeChanged() { if (!mCurrentThemePath.isEmpty()) mThemeWatcher->removePath(mCurrentThemePath); if (lxqtTheme.currentTheme().isValid()) { mCurrentThemePath = lxqtTheme.currentTheme().path(); mThemeWatcher->addPath(mCurrentThemePath); } } void LXQtModuleManager::startWm(LXQt::Settings *settings) { // if the WM is active do not run WM. // all window managers must set their name according to the spec if (!QString(NETRootInfo(QX11Info::connection(), NET::SupportingWMCheck).wmName()).isEmpty()) { mWmStarted = true; return; } if (mWindowManager.isEmpty()) { mWindowManager = settings->value("window_manager").toString(); } // If previuos WM was removed, we show dialog. if (mWindowManager.isEmpty() || ! findProgram(mWindowManager.split(' ')[0])) { mWindowManager = showWmSelectDialog(); settings->setValue("window_manager", mWindowManager); settings->sync(); } if (QFileInfo(mWindowManager).baseName() == "openbox") { // Default settings of openbox are copied by lxqt-common/startlxqt.in QString openboxSettingsPath = XdgDirs::configHome() + "/openbox/lxqt-rc.xml"; QStringList args; if(QFileInfo::exists(openboxSettingsPath)) args << "--config-file" << openboxSettingsPath; mWmProcess->start(mWindowManager, args); } else mWmProcess->start(mWindowManager); // other autostart apps will be handled after the WM becomes available // Wait until the WM loads QEventLoop waitLoop; mWaitLoop = &waitLoop; // add a timeout to avoid infinite blocking if a WM fail to execute. QTimer::singleShot(30 * 1000, &waitLoop, SLOT(quit())); waitLoop.exec(); mWaitLoop = NULL; // FIXME: blocking is a bad idea. We need to start as many apps as possible and // only wait for the start of WM when it's absolutely needed. // Maybe we can add a X-Wait-WM=true key in the desktop entry file? } void LXQtModuleManager::startProcess(const XdgDesktopFile& file) { if (!file.value("X-LXQt-Module", false).toBool()) { file.startDetached(); return; } QStringList args = file.expandExecString(); if (args.isEmpty()) { qCWarning(SESSION) << "Wrong desktop file" << file.fileName(); return; } LXQtModule* proc = new LXQtModule(file, this); connect(proc, SIGNAL(moduleStateChanged(QString,bool)), this, SIGNAL(moduleStateChanged(QString,bool))); proc->start(); QString name = QFileInfo(file.fileName()).fileName(); mNameMap[name] = proc; connect(proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(restartModules(int, QProcess::ExitStatus))); } void LXQtModuleManager::startProcess(const QString& name) { if (!mNameMap.contains(name)) { foreach (const XdgDesktopFile& file, XdgAutoStart::desktopFileList(false)) { if (QFileInfo(file.fileName()).fileName() == name) { startProcess(file); return; } } } } void LXQtModuleManager::stopProcess(const QString& name) { if (mNameMap.contains(name)) mNameMap[name]->terminate(); } QStringList LXQtModuleManager::listModules() const { return QStringList(mNameMap.keys()); } void LXQtModuleManager::startConfUpdate() { XdgDesktopFile desktop(XdgDesktopFile::ApplicationType, ":lxqt-confupdate", "lxqt-confupdate --watch"); desktop.setValue("Name", "LXQt config updater"); desktop.setValue("X-LXQt-Module", true); startProcess(desktop); } void LXQtModuleManager::restartModules(int exitCode, QProcess::ExitStatus exitStatus) { LXQtModule* proc = qobject_cast(sender()); Q_ASSERT(proc); if (!proc->isTerminating()) { QString procName = proc->file.name(); switch (exitStatus) { case QProcess::NormalExit: qCDebug(SESSION) << "Process" << procName << "(" << proc << ") exited correctly."; break; case QProcess::CrashExit: { qCDebug(SESSION) << "Process" << procName << "(" << proc << ") has to be restarted"; time_t now = time(NULL); mCrashReport[proc].prepend(now); while (now - mCrashReport[proc].back() > 60) mCrashReport[proc].pop_back(); if (mCrashReport[proc].length() >= MAX_CRASHES_PER_APP) { QMessageBox::warning(0, tr("Crash Report"), tr("%1 crashed too many times. Its autorestart has been disabled until next login.").arg(procName)); } else { proc->start(); return; } break; } } } mNameMap.remove(proc->fileName); proc->deleteLater(); } LXQtModuleManager::~LXQtModuleManager() { qApp->removeNativeEventFilter(this); qDeleteAll(mNameMap); delete mWmProcess; } /** * @brief this logs us out by terminating our session **/ void LXQtModuleManager::logout() { // modules ModulesMapIterator i(mNameMap); while (i.hasNext()) { i.next(); qCDebug(SESSION) << "Module logout" << i.key(); LXQtModule* p = i.value(); p->terminate(); } i.toFront(); while (i.hasNext()) { i.next(); LXQtModule* p = i.value(); if (p->state() != QProcess::NotRunning && !p->waitForFinished(2000)) { qCWarning(SESSION) << QString("Module '%1' won't terminate ... killing.").arg(i.key()); p->kill(); } } mWmProcess->terminate(); if (mWmProcess->state() != QProcess::NotRunning && !mWmProcess->waitForFinished(2000)) { qCWarning(SESSION) << QString("Window Manager won't terminate ... killing."); mWmProcess->kill(); } QCoreApplication::exit(0); } QString LXQtModuleManager::showWmSelectDialog() { WindowManagerList availableWM = getWindowManagerList(true); if (availableWM.count() == 1) return availableWM.at(0).command; WmSelectDialog dlg(availableWM); dlg.exec(); return dlg.windowManager(); } void LXQtModuleManager::resetCrashReport() { mCrashReport.clear(); } bool LXQtModuleManager::nativeEventFilter(const QByteArray & eventType, void * message, long * result) { if (eventType != "xcb_generic_event_t") // We only want to handle XCB events return false; if(!mWmStarted && mWaitLoop) { // all window managers must set their name according to the spec if (!QString(NETRootInfo(QX11Info::connection(), NET::SupportingWMCheck).wmName()).isEmpty()) { qCDebug(SESSION) << "Window Manager started"; mWmStarted = true; if (mWaitLoop->isRunning()) mWaitLoop->exit(); } } if (!mTrayStarted && QSystemTrayIcon::isSystemTrayAvailable() && mWaitLoop) { qCDebug(SESSION) << "System Tray started"; mTrayStarted = true; if (mWaitLoop->isRunning()) mWaitLoop->exit(); // window manager and system tray have started qApp->removeNativeEventFilter(this); } return false; } void lxqt_setenv(const char *env, const QByteArray &value) { wordexp_t p; wordexp(value, &p, 0); if (p.we_wordc == 1) { qCDebug(SESSION) << "Environment variable" << env << "=" << p.we_wordv[0]; qputenv(env, p.we_wordv[0]); } else { qCWarning(SESSION) << "Error expanding environment variable" << env << "=" << value; qputenv(env, value); } wordfree(&p); } void lxqt_setenv_prepend(const char *env, const QByteArray &value, const QByteArray &separator) { QByteArray orig(qgetenv(env)); orig = orig.prepend(separator); orig = orig.prepend(value); qCDebug(SESSION) << "Setting special" << env << " variable:" << orig; lxqt_setenv(env, orig); } LXQtModule::LXQtModule(const XdgDesktopFile& file, QObject* parent) : QProcess(parent), file(file), fileName(QFileInfo(file.fileName()).fileName()), mIsTerminating(false) { connect(this, SIGNAL(stateChanged(QProcess::ProcessState)), SLOT(updateState(QProcess::ProcessState))); } void LXQtModule::start() { mIsTerminating = false; QStringList args = file.expandExecString(); QString command = args.takeFirst(); QProcess::start(command, args); } void LXQtModule::terminate() { mIsTerminating = true; QProcess::terminate(); } bool LXQtModule::isTerminating() { return mIsTerminating; } void LXQtModule::updateState(QProcess::ProcessState newState) { if (newState != QProcess::Starting) emit moduleStateChanged(fileName, (newState == QProcess::Running)); }