You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
statusbar/src/capplications.cpp

219 lines
5.9 KiB
C++

/*
* Copyright (C) 2021 CutefishOS Team.
*
* Author: cutefishos <cutefishos@foxmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "capplications.h"
#include <QRegularExpression>
#include <QSettings>
#include <QLocale>
#include <QDirIterator>
#include <QDir>
static CApplications *SELF = nullptr;
static QString s_systemAppFolder = "/usr/share/applications";
static QByteArray detectDesktopEnvironment()
{
const QByteArray desktop = qgetenv("XDG_CURRENT_DESKTOP");
if (!desktop.isEmpty())
return desktop.toUpper();
return QByteArray("UNKNOWN");
}
CApplications *CApplications::self()
{
if (!SELF)
SELF = new CApplications;
return SELF;
}
CApplications::CApplications(QObject *parent)
: QObject(parent)
, m_watcher(new QFileSystemWatcher(this))
{
m_watcher->addPath(s_systemAppFolder);
connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &CApplications::refresh);
refresh();
}
CApplications::~CApplications()
{
while (!m_items.isEmpty())
delete m_items.takeFirst();
}
CAppItem *CApplications::find(const QString &fileName)
{
for (CAppItem *item : m_items)
if (item->path == fileName)
return item;
return nullptr;
}
CAppItem *CApplications::matchItem(quint32 pid)
{
QStringList commands = commandFromPid(pid);
// The value returned from the commandFromPid() may be empty.
// Calling first() and last() below will cause the statusbar to crash.
if (commands.isEmpty())
return nullptr;
QString command = commands.first();
QString commandName = commands.last();
if (command.isEmpty())
return nullptr;
for (CAppItem *item : m_items) {
if (item->fullExec == command ||
item->exec == command ||
item->fullExec == commandName ||
item->exec == commandName) {
return item;
}
}
return nullptr;
}
void CApplications::refresh()
{
QStringList addedEntries;
for (CAppItem *item : m_items)
addedEntries.append(item->path);
QStringList allEntries;
QDirIterator it(s_systemAppFolder, { "*.desktop" }, QDir::NoFilter, QDirIterator::Subdirectories);
while (it.hasNext()) {
const QString &filePath = it.next();
if (!QFile::exists(filePath))
continue;
allEntries.append(filePath);
}
for (const QString &filePath : allEntries) {
if (!addedEntries.contains(filePath)) {
addApplication(filePath);
}
}
for (CAppItem *item : m_items) {
if (!allEntries.contains(item->path)) {
removeApplication(item);
}
}
}
void CApplications::addApplication(const QString &filePath)
{
if (find(filePath))
return;
QSettings desktop(filePath, QSettings::IniFormat);
desktop.setIniCodec("UTF-8");
desktop.beginGroup("Desktop Entry");
// Skip...
if (desktop.contains("OnlyShowIn")) {
const QString &value = desktop.value("OnlyShowIn").toString();
if (!value.contains(detectDesktopEnvironment(), Qt::CaseInsensitive)) {
return;
}
}
if (desktop.value("NoDisplay").toBool() ||
desktop.value("Hidden").toBool()) {
return;
}
// Local Name
QString localName = desktop.value(QString("Name[%1]").arg(QLocale::system().name())).toString();
if (localName.isEmpty())
localName = desktop.value("Name").toString();
// Exec
QString simplifiedExec = desktop.value("Exec").toString();
simplifiedExec.remove(QRegularExpression("%."));
simplifiedExec.remove(QRegularExpression("^\""));
// appExec.remove(QRegularExpression(" *$"));
simplifiedExec = simplifiedExec.simplified();
// New data
CAppItem *item = new CAppItem;
item->path = filePath;
item->localName = localName;
item->name = desktop.value("Name").toString();;
item->comment = desktop.value("Comment").toString();
item->icon = desktop.value("Icon").toString();
item->fullExec = desktop.value("Exec").toString();
item->exec = simplifiedExec;
m_items.append(item);
}
void CApplications::removeApplication(CAppItem *item)
{
m_items.removeOne(item);
delete item;
}
QStringList CApplications::commandFromPid(quint32 pid)
{
QFile file(QString("/proc/%1/cmdline").arg(pid));
if (file.open(QIODevice::ReadOnly)) {
QByteArray cmd = file.readAll();
// ref: https://github.com/KDE/kcoreaddons/blob/230c98aa7e01f9e36a9c2776f3633182e6778002/src/lib/util/kprocesslist_unix.cpp#L137
if (!cmd.isEmpty()) {
// extract non-truncated name from cmdline
int zeroIndex = cmd.indexOf('\0');
int processNameStart = cmd.lastIndexOf('/', zeroIndex);
if (processNameStart == -1) {
processNameStart = 0;
} else {
processNameStart++;
}
QString name = QString::fromLocal8Bit(cmd.mid(processNameStart, zeroIndex - processNameStart));
cmd.replace('\0', ' ');
QString command = QString::fromLocal8Bit(cmd).trimmed();
// There may be parameters.
if (command.split(' ').size() > 1) {
command = command.split(' ').first();
}
return { command, name };
}
}
return QStringList();
}