#include <QDir>
#include <QSettings>
#include <QLocale>
#include <QApplication>
#include <QStandardPaths>
#include "unistd.h"
#include "xsession.h"
#include "globals.h"
#include "sessionsiconholder.h"
struct DisplayConfig {
QString nameChange;
QRegularExpression regex;
int priority;
QChar type;
};
static QList< DisplayConfig > priorityList;
void XSession::init(const QString& name, const QString& exec, const QString& tryExec,
const QString& comment, const QString& icon) {
this->name_ = name;
this->exec_ = exec;
this->tryExec_ = tryExec;
this->comment_ = comment;
this->icon_ = icon;
this->priority_ = 0;
}
bool XSession::init(const QString& filename) {
this->priority_ = 0;
QSettings settings(filename, QSettings::IniFormat);
settings.setIniCodec("UTF-8");
settings.beginGroup("Desktop Entry");
if (settings.value("NoDisplay").toString().compare("true") == 0 ||
settings.value("Hidden").toString().compare("true") == 0) {
return false;
}
if (!settings.contains("Exec")) {
return false;
}
QString exec(settings.value("Exec").toString());
QString tryExec(settings.value("TryExec").toString());
QString locale(QLocale::system().name());
QString language(locale.split("_").at(0));
QString defaultName("");
if (settings.contains("Name"))
defaultName = settings.value("Name").toString();
QString name;
if (settings.contains("Name[" + locale + "]")) {
name = settings.value("Name[" + locale + "]").toString();
} else if (settings.contains("Name[" + language + "]")) {
name = settings.value("Name[" + language + "]").toString();
} else if (!defaultName.isEmpty()) {
name = defaultName;
} else {
return false;
}
QString comment;
if (settings.contains("Comment[" + locale + "]")) {
comment = settings.value("Comment[" + locale + "]").toString();
} else if (settings.contains("Comment[" + language + "]")) {
comment = settings.value("Comment[" + language + "]").toString();
} else {
comment = settings.value("Comment").toString();
}
QString icon(settings.value("Icon").toString());
if (QDir::isRelativePath(icon)) {
// icons with relative paths are too complicated to find
// see http://freedesktop.org/wiki/Specifications/icon-theme-spec
// let's just ignore them
icon = QString();
}
// check for xsession priority/name change
for (auto &entry : priorityList) {
if ((entry.type == 't' && entry.regex.match(defaultName).hasMatch()) ||
(entry.type == 'e' && entry.regex.match(exec).hasMatch()) ||
(entry.type == 'f' && entry.regex.match(filename).hasMatch())) {
this->priority_ = entry.priority;
if (entry.nameChange.isEmpty())
continue;
// check if we have a +/- to append/prepend the given text
// TODO support localized name changes...
QChar pend = entry.nameChange.at(0);
if (pend == '+') {
name.append(entry.nameChange.mid(1));
continue;
} else if (pend == '-') {
name.prepend(entry.nameChange.mid(1));
} else {
name = entry.nameChange;
}
}
}
this->name_ = name;
this->exec_ = exec;
this->tryExec_ = tryExec;
this->comment_ = comment;
this->icon_ = icon;
return true;
}
bool XSession::isActive() const {
return true;
}
QString XSession::checkCanRunInternal() const {
QString exe;
if (this->tryExec_.isEmpty()) {
exe = this->exec_.left(this->exec_.indexOf(' ')); // -1 means entire string
} else {
exe = this->tryExec_;
}
QFileInfo fi(exe);
if (!fi.isAbsolute()) {
if (!QStandardPaths::findExecutable(exe).isEmpty())
return QString();
}
if (fi.isFile() && fi.isExecutable())
return QString();
// Not found
return QObject::trUtf8("Binary %1 not found.").arg(exe);
}
bool XSession::isLocked() const {
return false;
}
QIcon XSession::icon() const {
QIcon retIcon;
if (!this->icon_.isEmpty()) {
retIcon = SessionsIconHolder::get()->getIcon(this->icon_);
}
if (retIcon.isNull()) {
QString icon;
if (this->exec_.contains("kde", Qt::CaseInsensitive)) {
icon = "kde";
} else if (this->exec_.contains("gnome", Qt::CaseInsensitive)) {
icon = "gnome";
} else if (this->exec_.contains("xfce", Qt::CaseInsensitive)) {
icon = "xfce";
} else if (this->exec_.startsWith("i3", Qt::CaseInsensitive) || this->exec_.contains("/i3", Qt::CaseInsensitive)) {
icon = "i3";
} else if (this->exec_.contains("lxde", Qt::CaseInsensitive)) {
icon = "lxde";
} else if (this->exec_.contains("term", Qt::CaseInsensitive)) {
icon = "term";
} else {
icon = "linux";
}
retIcon = SessionsIconHolder::get()->getIcon(icon);
}
return retIcon;
}
bool XSession::prepareRun() const {
return canRun(true);
}
void XSession::run() const {
QString command;
QString firstToken = this->exec_.left(this->exec_.indexOf(' '));
if (!firstToken.contains('=')) { // First token doesn't appear to be environment modification, try to prefix with exec
command += "exec ";
}
command += exec_;
qDebug() << "Running via /bin/sh:" << command;
QByteArray cmdbin = command.toUtf8();
char *argv[4] = { "/bin/sh", "-c", cmdbin.data(), nullptr };
execv("/bin/sh", argv);
}
int XSession::type() const {
return Session::XSESSION;
}
QList<Session*> XSession::readSessions(const QString& path) {
// load the priorities from the config file
loadXSessionsConfig();
QList<Session*> retval;
foreach (QFileInfo fi, QDir(path).entryInfoList(QStringList("*.desktop"))) {
if (fi.baseName().compare("default") == 0) {
continue;
}
XSession* session = new XSession;
if (session->init(fi.absoluteFilePath())) {
retval.append(session);
} else {
delete session;
}
}
return retval;
}
bool XSession::operator<(const Session& other) const {
int p0 = this->priority();
int p1 = other.priority();
if (p0 < p1) return true;
if (p0 == p1) {
QString d0 = this->shortDescription();
QString d1 = other.shortDescription();
return d0.localeAwareCompare(d1) < 0;
}
return false;
}
bool XSession::containsKeywords(const QList<QString>& keywords) const {
for (int j = 0; j < keywords.length(); ++j) {
if (!this->shortDescription().contains(keywords[j], Qt::CaseInsensitive)
&& !this->description().contains(keywords[j], Qt::CaseInsensitive)) {
return false;
}
}
return true;
}
void XSession::loadXSessionsConfig() {
int idx = 0;
QFile inputFile(CONFIG_FILE_XSESSIONS);
if (!inputFile.open(QIODevice::ReadOnly))
return;
QTextStream in(&inputFile);
in.setCodec("UTF-8");
while (!in.atEnd()) {
QString line = in.readLine();
idx++;
if (line.isEmpty())
continue;
// start parsing line, the expected format is:
// [tef]/<regex>/<modifiers>/<priority>/[+-]<text>
// - t, e, f denotes to match against xsession's title, exec command, filename, respectively.
// - regex to match
// - modifiers to use when matching (e.g. insensitive)
// - priority to set for the xsession
// - text to replace the matched title with, only valid in combination with 't'. Optional +/- to append/prepend.
QChar matchType = line.at(0);
if (!QString("tef").contains(matchType))
continue;
QChar delim = line.at(1);
int first = find(delim, 2, line);
if (first == -1)
continue;
int second = find(delim, first + 1, line);
if (second == -1)
continue;
int third = find(delim, second + 1, line);
QString regex = line.mid(2, first - 2).replace(QString("\\") + delim, QString(delim));
QString flags = line.mid(first + 1, second - first - 1);
QString nameChanges("");
int priorityLength = line.length() - second;
if (third != -1) {
nameChanges = line.mid(third + 1);
priorityLength = third - second - 1;
}
int priority = line.mid(second + 1, priorityLength).toInt();
QRegularExpression::PatternOptions opts = QRegularExpression::NoPatternOption;
if (flags.contains("i"))
opts |= QRegularExpression::CaseInsensitiveOption;
QRegularExpression re(regex, opts);
if (!re.isValid()) {
qWarning() << "Invalid regex:" << regex << "on line" << idx;
continue;
}
DisplayConfig config;
config.type = matchType;
config.regex = re;
config.nameChange = nameChanges;
config.priority = priority;
priorityList.append(config);
}
inputFile.close();
}