diff --git a/CMakeLists.txt b/CMakeLists.txt index 49713da..6ff3e6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRCS src/volume.cpp src/processprovider.cpp src/activity.cpp + src/capplications.cpp src/systemtray/statusnotifieritemsource.cpp src/systemtray/systemtraytypes.cpp diff --git a/qml/main.qml b/qml/main.qml index defde75..6e94b1d 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -49,9 +49,8 @@ Item { StandardItem { id: acticityItem Layout.fillHeight: true - Layout.preferredWidth: acticityLayout.implicitWidth + FishUI.Units.largeSpacing - - visible: acticity.title + Layout.preferredWidth: acticityLayout.implicitWidth ? acticityLayout.implicitWidth + FishUI.Units.largeSpacing + : 0 onClicked: acticityMenu.open() RowLayout { @@ -61,18 +60,20 @@ Item { anchors.rightMargin: FishUI.Units.smallSpacing spacing: FishUI.Units.largeSpacing - Image { - id: acticityIcon - width: 16 - height: 16 - sourceSize: Qt.size(width, height) - source: acticity.icon ? "image://icontheme/" + acticity.icon : "" - visible: status === Image.Ready - } +// Image { +// id: acticityIcon +// width: 16 +// height: 16 +// sourceSize: Qt.size(width, height) +// source: acticity.icon ? "image://icontheme/" + acticity.icon : "" +// visible: status === Image.Ready +// asynchronous: true +// } Label { id: acticityLabel text: acticity.title + visible: text } } } diff --git a/src/activity.cpp b/src/activity.cpp index c264b3f..a68ab27 100644 --- a/src/activity.cpp +++ b/src/activity.cpp @@ -31,6 +31,7 @@ Activity::Activity(QObject *parent) : QObject(parent) + , m_cApps(CApplications::self()) { onActiveWindowChanged(); @@ -72,85 +73,24 @@ void Activity::onActiveWindowChanged() m_pid = info.pid(); m_windowClass = info.windowClassClass().toLower(); - if (!matchInfo()) { - QString title = info.visibleName(); - if (title != m_title) { - m_title = title; - emit titleChanged(); - m_icon.clear(); - emit iconChanged(); - } - } -} + CAppItem *item = m_cApps->matchItem(m_pid); -bool Activity::matchInfo() -{ - QString command = commandFromPid(m_pid); - - // TODO: optimization - QDirIterator it("/usr/share/applications", { "*.desktop" }, - QDir::NoFilter, QDirIterator::Subdirectories); - - while (it.hasNext()) { - const QString &filePath = it.next(); - - QSettings desktop(filePath, QSettings::IniFormat); - desktop.setIniCodec("UTF-8"); - desktop.beginGroup("Desktop Entry"); + if (item) { + m_title = item->localName; + emit titleChanged(); - if (desktop.value("NoDisplay").toBool() || - desktop.value("Hidden").toBool()) { - continue; + if (m_icon != item->icon) { + m_icon = item->icon; + emit iconChanged(); } - QString exec = desktop.value("Exec").toString(); - exec.remove(QRegularExpression("%.")); - exec.remove(QRegularExpression("^\"")); - exec = exec.simplified(); - - if (command == exec) { - QString name = desktop.value(QString("Name[%1]").arg(QLocale::system().name())).toString(); - if (name.isEmpty()) - name = desktop.value("Name").toString(); - - m_title = name; + } else { + QString title = info.visibleName(); + if (title != m_title) { + m_title = title; emit titleChanged(); - - m_icon = desktop.value("Icon").toString(); + m_icon.clear(); emit iconChanged(); - - return true; } } - - return false; -} - -QString Activity::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(); - return name; - } - } - - return QString(); } diff --git a/src/activity.h b/src/activity.h index eacfdde..6cec4d9 100644 --- a/src/activity.h +++ b/src/activity.h @@ -21,6 +21,7 @@ #define ACTIVITY_H #include +#include "capplications.h" class Activity : public QObject { @@ -38,15 +39,13 @@ public: private slots: void onActiveWindowChanged(); - bool matchInfo(); - - QString commandFromPid(quint32 pid); signals: void titleChanged(); void iconChanged(); private: + CApplications *m_cApps; QString m_title; QString m_icon; QString m_windowClass; diff --git a/src/capplications.cpp b/src/capplications.cpp new file mode 100644 index 0000000..3b8bb5a --- /dev/null +++ b/src/capplications.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: cutefishos + * + * 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 . + */ + +#include "capplications.h" + +#include +#include +#include + +#include +#include + +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); + 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(); + return { command, name }; + } + } + + return QStringList(); +} + diff --git a/src/capplications.h b/src/capplications.h new file mode 100644 index 0000000..be5d2ee --- /dev/null +++ b/src/capplications.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: cutefishos + * + * 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 . + */ + +#ifndef CAPPLICATIONS_H +#define CAPPLICATIONS_H + +#include +#include + +class CAppItem +{ +public: + QString path; + QString localName; + QString name; + QString comment; + QString icon; + QString fullExec; + QString exec; +}; + +class CApplications : public QObject +{ + Q_OBJECT + +public: + static CApplications *self(); + explicit CApplications(QObject *parent = nullptr); + ~CApplications(); + + CAppItem *find(const QString &fileName); + CAppItem *matchItem(quint32 pid); + +private: + void refresh(); + void addApplication(const QString &filePath); + void removeApplication(CAppItem *item); + + QStringList commandFromPid(quint32 pid); + +private: + QFileSystemWatcher *m_watcher; + QList m_items; +}; + +#endif // CAPPLICATIONS_H