You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
490 lines
14 KiB
490 lines
14 KiB
/* 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 <petr@scribus.info>
|
|
* Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
|
|
*
|
|
* 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 <LXQt/Settings>
|
|
#include <XdgAutoStart>
|
|
#include <XdgDirs>
|
|
#include <unistd.h>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QMessageBox>
|
|
#include <QSystemTrayIcon>
|
|
#include <QFileInfo>
|
|
#include <QFile>
|
|
#include <QDir>
|
|
#include <QFileSystemWatcher>
|
|
#include <QDateTime>
|
|
#include "wmselectdialog.h"
|
|
#include "windowmanager.h"
|
|
#include <wordexp.h>
|
|
#include "log.h"
|
|
|
|
#include <KWindowSystem/KWindowSystem>
|
|
#include <KWindowSystem/netwm.h>
|
|
|
|
#include <QX11Info>
|
|
|
|
#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<XdgDesktopFile*> 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<LXQtTheme> &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<LXQtModule*>(sender());
|
|
if (nullptr == proc) {
|
|
qCWarning(SESSION) << "Got an invalid (null) module to restart. Ignoring it";
|
|
return;
|
|
}
|
|
|
|
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("<b>%1</b> 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);
|
|
|
|
// We disconnect the finished signal before deleting the process. We do
|
|
// this to prevent a crash that results from a state change signal being
|
|
// emmited while deleting a crashing module.
|
|
// If the module is still connect restartModules will be called with a
|
|
// invalid sender.
|
|
|
|
ModulesMapIterator i(mNameMap);
|
|
while (i.hasNext())
|
|
{
|
|
i.next();
|
|
|
|
auto p = i.value();
|
|
disconnect(p, SIGNAL(finished(int, QProcess::ExitStatus)), 0, 0);
|
|
|
|
delete p;
|
|
mNameMap[i.key()] = nullptr;
|
|
}
|
|
|
|
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));
|
|
}
|