summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorFabien Proriol <fabien.proriol@kazoe.org>2025-05-25 12:13:31 +0200
committerFabien Proriol <fabien.proriol@kazoe.org>2025-05-25 12:13:31 +0200
commit1dbc0e3c88ba271ba35bc3f82e7864c4f35e1236 (patch)
tree8c491cd196e2eff4c59f8c23f566f7ff26981586 /src/lib
Initial Commit
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/debug.cpp4
-rw-r--r--src/lib/debug.h9
-rw-r--r--src/lib/xdgautostart.cpp38
-rw-r--r--src/lib/xdgautostart.h24
-rw-r--r--src/lib/xdgbasedir.cpp79
-rw-r--r--src/lib/xdgbasedir.h97
-rw-r--r--src/lib/xdgdesktopentries.cpp202
-rw-r--r--src/lib/xdgdesktopentries.h58
-rw-r--r--src/lib/xdgdesktopfilter.cpp131
-rw-r--r--src/lib/xdgdesktopfilter.h49
-rw-r--r--src/lib/xdgentries.cpp128
-rw-r--r--src/lib/xdgentries.h41
-rw-r--r--src/lib/xdgentry.cpp315
-rw-r--r--src/lib/xdgentry.h67
-rw-r--r--src/lib/xdgnotificationactions.cpp63
-rw-r--r--src/lib/xdgnotificationactions.h40
-rw-r--r--src/lib/xdgnotificationmanager.cpp173
-rw-r--r--src/lib/xdgnotificationmanager.h55
-rw-r--r--src/lib/xdgnotificationmessage.cpp119
-rw-r--r--src/lib/xdgnotificationmessage.h67
-rw-r--r--src/lib/xdgnotificationserver.cpp87
-rw-r--r--src/lib/xdgnotificationserver.h42
-rw-r--r--src/lib/xdgnotifier.cpp99
-rw-r--r--src/lib/xdgnotifier.h28
-rw-r--r--src/lib/xdgstatusnotifierhost.cpp187
-rw-r--r--src/lib/xdgstatusnotifierhost.h44
-rw-r--r--src/lib/xdgstatusnotifieritem.cpp169
-rw-r--r--src/lib/xdgstatusnotifieritem.h119
-rw-r--r--src/lib/xdgstatusnotifierwatcher.cpp116
-rw-r--r--src/lib/xdgstatusnotifierwatcher.h48
30 files changed, 2698 insertions, 0 deletions
diff --git a/src/lib/debug.cpp b/src/lib/debug.cpp
new file mode 100644
index 0000000..565bdbc
--- /dev/null
+++ b/src/lib/debug.cpp
@@ -0,0 +1,4 @@
+#include "debug.h"
+
+Q_LOGGING_CATEGORY(AUTO_START, "xdg.auto_start")
+Q_LOGGING_CATEGORY(SYSTEM_TRAY, "xdg.system_tray")
diff --git a/src/lib/debug.h b/src/lib/debug.h
new file mode 100644
index 0000000..978b470
--- /dev/null
+++ b/src/lib/debug.h
@@ -0,0 +1,9 @@
+#ifndef DEBUG_H
+#define DEBUG_H
+
+#include <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(AUTO_START)
+Q_DECLARE_LOGGING_CATEGORY(SYSTEM_TRAY)
+
+#endif // DEBUG_H
diff --git a/src/lib/xdgautostart.cpp b/src/lib/xdgautostart.cpp
new file mode 100644
index 0000000..bd71261
--- /dev/null
+++ b/src/lib/xdgautostart.cpp
@@ -0,0 +1,38 @@
+#include "xdgautostart.h"
+#include "xdgbasedir.h"
+#include "xdgentry.h"
+#include "debug.h"
+
+xdg::AutoStart::AutoStart(QObject *parent)
+ : QObject{parent}
+{
+ qCDebug(AUTO_START) << "Create Auto start";
+ QStringList configdirs = xdg::configDirs();
+ for (auto dir = configdirs.rbegin(); dir != configdirs.rend(); ++dir)
+ {
+ m_entries.addDirectory(*dir + "/autostart");
+ }
+ m_entries.addDirectory(xdg::configHome() + "/autostart");
+}
+
+void xdg::AutoStart::start(QString key, QString value)
+{
+ qCDebug(AUTO_START) << "Start " << key << value;
+ for(xdg::Entry *entry: m_entries.getEntries())
+ {
+ if((!value.isNull()) && (!key.isNull()))
+ {
+ QString c = entry->data(key);
+ if(c.trimmed().toLower() == value.trimmed().toLower())
+ {
+ qCDebug(AUTO_START) << "Start app " << entry->appId() << "with" << key << value;
+ entry->start(QStringList());
+ }
+ }
+ else
+ {
+ qCDebug(AUTO_START) << "Start app " << entry->appId();
+ entry->start(QStringList());
+ }
+ }
+}
diff --git a/src/lib/xdgautostart.h b/src/lib/xdgautostart.h
new file mode 100644
index 0000000..fc6c02d
--- /dev/null
+++ b/src/lib/xdgautostart.h
@@ -0,0 +1,24 @@
+#ifndef XDGAUTOSTART_H
+#define XDGAUTOSTART_H
+
+#include <QObject>
+#include "xdgentries.h"
+
+
+namespace xdg {
+ class AutoStart : public QObject
+ {
+ Q_OBJECT
+ public:
+ explicit AutoStart(QObject *parent = nullptr);
+ xdg::Entries m_entries;
+
+ public slots:
+ void start(QString key = QString(), QString value = QString());
+
+ signals:
+
+ };
+}
+
+#endif // XDGAUTOSTART_H
diff --git a/src/lib/xdgbasedir.cpp b/src/lib/xdgbasedir.cpp
new file mode 100644
index 0000000..1f84109
--- /dev/null
+++ b/src/lib/xdgbasedir.cpp
@@ -0,0 +1,79 @@
+#include "xdgbasedir.h"
+#include <QStringList>
+
+static inline QString user()
+{
+ QString user = QString::fromUtf8(qgetenv("USER"));
+ if(user.isEmpty()) return "root";
+ return user;
+}
+
+static inline QString home()
+{
+ QString home = QString::fromUtf8(qgetenv("HOME"));
+ if(home.isEmpty())
+ {
+ QString u = user();
+ if(u == "root")
+ return "/root";
+ else
+ return "/home/" + u;
+ }
+ return home;
+}
+
+static inline QString envOrDefault(const char *name, const QString &def)
+{
+ QString env = QString::fromUtf8(qgetenv(name));
+ if(env.isEmpty())
+ {
+ return def;
+ }
+ return env;
+}
+
+QString xdg::dataHome()
+{
+ return envOrDefault("XDG_DATA_HOME", home() + "/.local/share");
+}
+
+QString xdg::configHome()
+{
+ return envOrDefault("XDG_CONFIG_HOME", home() + "/.config");
+}
+
+QString xdg::cacheHome()
+{
+ return envOrDefault("XDG_CACHE_HOME", home() + "/.cache");
+}
+
+QString xdg::stateHome()
+{
+ return envOrDefault("XDG_STATE_HOME", home() + "/.local/state");
+}
+
+
+QStringList xdg::dataDirs()
+{
+ QString dataDirs = QString::fromUtf8(qgetenv("XDG_DATA_DIRS"));
+ if(dataDirs.isEmpty())
+ {
+ return QStringList() << "/usr/local/share" << "/usr/share";
+ }
+ return dataDirs.split(":");
+}
+
+QStringList xdg::configDirs()
+{
+ QString configDirs = QString::fromUtf8(qgetenv("XDG_CONFIG_DIRS"));
+ if(configDirs.isEmpty())
+ {
+ return QStringList() << "/etc/xdg";
+ }
+ return configDirs.split(":");
+}
+
+QString xdg::runtimeDir()
+{
+ return envOrDefault("XDG_RUNTIME_DIR", QString("/run/user/%i").arg(user()));
+}
diff --git a/src/lib/xdgbasedir.h b/src/lib/xdgbasedir.h
new file mode 100644
index 0000000..bd7d688
--- /dev/null
+++ b/src/lib/xdgbasedir.h
@@ -0,0 +1,97 @@
+/*! \file xdgbasedir.h
+ \brief Implementation of basedir-spec
+ \author Fabien Proriol
+
+ Implementation of https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+*/
+
+#ifndef XDGBASEDIR_H
+#define XDGBASEDIR_H
+
+#include <QString>
+
+//! XDG basedir implementation
+namespace xdg {
+/*!
+ * \addtogroup XDG
+ * @{
+ */
+
+/*! \fn QString dataHome()
+ \brief Return XDG_DATA_HOME
+ XDG_DATA_HOME defines the base directory relative to
+ which user-specific data files should be stored.
+ \return $XDG_DATA_HOME content or $HOME/.local/share if not set
+*/
+ QString dataHome();
+
+
+/*! \fn QString configHome()
+ \brief Return XDG_CONFIG_HOME
+ XDG_CONFIG_HOME defines the base directory relative to
+ which user-specific configuration files should be stored.
+ \return $XDG_CONFIG_HOME content or $HOME/.config if not set
+*/
+ QString configHome();
+
+
+/*! \fn QString stateHome()
+ \brief Return XDG_STATE_HOME
+ XDG_STATE_HOME defines the base directory relative to
+ which user-specific state files should be stored.
+ \return $XDG_STATE_HOME content or $HOME/.local/state if not set
+*/
+ QString stateHome();
+
+
+/*! \fn QStringList dataDirs()
+ \brief Return XDG_DATA_DIRS
+ XDG_DATA_DIRS defines the preference-ordered set of
+ base directories to search for data files in addition
+ to the $XDG_DATA_HOME base directory.
+ The directories in $XDG_DATA_DIRS should be seperated
+ with a colon ':'.
+ \return $XDG_DATA_DIRS content or /usr/local/share:/usr/share if not set
+*/
+ QStringList dataDirs();
+
+/*! \fn QStringList dataDirs()
+ \brief Return XDG_CONFIG_DIRS
+ XDG_CONFIG_DIRS defines the preference-ordered set of
+ base directories to search for configuration files in addition
+ to the $XDG_CONFIG_HOME base directory.
+ The directories in $XDG_CONFIG_DIRS should be seperated
+ with a colon ':'.
+ \return $XDG_CONFIG_DIRS content or /etc/xdg if not set
+*/
+ QStringList configDirs();
+
+
+/*! \fn QString cacheHome()
+ \brief Return XDG_CONFIG_DIRS
+ XDG_CACHE_HOME defines the base directory relative to
+ which user-specific non-essential data files should be
+ stored.
+ If $XDG_CACHE_HOME is either not set or empty, a default
+ equal to $HOME/.cache should be used.
+ \return $XDG_CONFIG_DIRS content or $HOME/.cache if not set
+*/
+ QString cacheHome();
+
+
+/*! \fn QString runtimeDir()
+ \brief Return XDG_CONFIG_DIRS
+ XDG_RUNTIME_DIR defines the base directory relative to
+ which user-specific non-essential runtime files and other
+ file objects (such as sockets, named pipes, ...) should
+ be stored. The directory MUST be owned by the user,
+ and he MUST be the only one having read and write access
+ to it. Its Unix access mode MUST be 0700.
+ \return $XDG_RUNTIME_DIR content or emit a warning and return /run/user/$USER
+*/
+ QString runtimeDir();
+
+/*! @} End of XDG*/
+}
+
+#endif // XDGBASEDIR_H
diff --git a/src/lib/xdgdesktopentries.cpp b/src/lib/xdgdesktopentries.cpp
new file mode 100644
index 0000000..d1a3f95
--- /dev/null
+++ b/src/lib/xdgdesktopentries.cpp
@@ -0,0 +1,202 @@
+#include "xdgdesktopentries.h"
+#include "xdgentry.h"
+#include "xdgbasedir.h"
+#include <QDir>
+#include <QMutexLocker>
+#include <qdebug.h>
+#include <signal.h>
+
+
+
+xdg::DesktopEntries::DesktopEntries(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ QStringList datadirs = xdg::dataDirs();
+ for (auto dir = datadirs.rbegin(); dir != datadirs.rend(); ++dir)
+ {
+ m_entries.addDirectory(*dir + "/applications");
+ }
+ m_entries.addDirectory(xdg::dataHome() + "/applications");
+ QObject::connect(&m_entries, &xdg::Entries::entryDataChanged, this, &xdg::DesktopEntries::_dataChanged);
+ QObject::connect(&m_entries, &xdg::Entries::raiseProcess, this, &xdg::DesktopEntries::raiseProcess);
+ QObject::connect(&m_entries, &xdg::Entries::startCreateEntry, [this](int i){
+ beginInsertRows(QModelIndex(), i, i);
+ });
+ QObject::connect(&m_entries, &xdg::Entries::startCreateEntry, [this](int i){
+ endInsertRows();
+ emit categoriesChanged(categories());
+ });
+
+ QObject::connect(&m_entries, &xdg::Entries::startRemoveEntry, [this](int i){
+ beginRemoveRows(QModelIndex(), i, i);
+ });
+ QObject::connect(&m_entries, &xdg::Entries::startRemoveEntry, [this](int i){
+ endRemoveRows();
+ emit categoriesChanged(categories());
+ });
+}
+
+int xdg::DesktopEntries::rowCount(const QModelIndex &parent) const
+{
+ Q_UNUSED(parent);
+ return m_entries.count();
+}
+
+QVariant xdg::DesktopEntries::data(const QModelIndex &index, int role) const
+{
+ if(index.row() > m_entries.count())
+ return QVariant();
+
+ const xdg::Entry *entry = m_entries.getEntry(index.row());
+
+ switch(role) {
+ case Qt::DisplayRole:
+ case AppNameRole:
+ case NameRole:
+ return entry->data(XDG_NAME);
+ case ExecRole:
+ return entry->data(XDG_EXEC);
+ case IconRole:
+ return entry->data(XDG_ICON);
+ case CategoryRole:
+ return entry->data(XDG_CATEGORIES);
+ case NoDisplayRole:
+ return entry->data(XDG_NODISPLAY);
+ case KeywordsRole:
+ return entry->data(XDG_KEYWORDS);
+ case PathRole:
+ case DesktopFileRole:
+ return entry->path();
+ case AppIdRole:
+ return entry->appId();
+ case EntryRole:
+ {
+ QVariant ventry;
+ ventry.setValue(entry);
+ return ventry;
+ }
+ }
+ qWarning() << "Not managed role" << role;
+ return QVariant();
+}
+
+QHash<int, QByteArray> xdg::DesktopEntries::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
+ roles[IconRole] = "appIcon";
+ roles[NameRole] = "name";
+ roles[AppNameRole] = "appName";
+ roles[ExecRole] = "exec";
+ roles[PathRole] = "path";
+ roles[NoDisplayRole] = "noDisplay";
+ roles[CategoryRole] = "category";
+ roles[KeywordsRole] = "keywords";
+ roles[DesktopFileRole] = "desktopFile";
+ roles[AppIdRole] = "appId";
+ roles[EntryRole] = "entry";
+ return roles;
+}
+
+QStringList xdg::DesktopEntries::categories()
+{
+ QStringList ret;
+ for(xdg::Entry *entry: m_entries.getEntries())
+ {
+ QString c = entry->data(XDG_CATEGORIES);
+ if(!ret.contains(c))
+ ret.append(c);
+ }
+ return ret;
+}
+
+void xdg::DesktopEntries::start(QString path, const QStringList& args)
+{
+ for(xdg::Entry *entry: m_entries.getEntries())
+ {
+ if(entry->path() == path)
+ entry->start(args);
+ }
+}
+
+void xdg::DesktopEntries::stop(QString path, const QStringList& args)
+{
+ bool isPid;
+ int pid = path.toInt(&isPid, 10);
+ for(xdg::Entry *entry: m_entries.getEntries())
+ {
+ if((isPid && entry->pid() == pid) || (entry->path() == path))
+ {
+ entry->stop(args);
+ return;
+ }
+ }
+ if(isPid) kill(pid, SIGTERM);
+}
+
+QString xdg::DesktopEntries::appIdData(QString appid, QString key)
+{
+ for(xdg::Entry *entry: m_entries.getEntries())
+ {
+ if(entry->appId() == appid)
+ return entry->data(key);
+ }
+ return QString();
+}
+
+bool xdg::DesktopEntries::_contains(QString path)
+{
+ for(const xdg::Entry *entry: m_entries.getEntries())
+ {
+ if(entry->path() == path)
+ return true;
+ }
+ return false;
+}
+
+QStringList xdg::DesktopEntries::_getEntriesFrom(QString path)
+{
+ QStringList entries;
+ for(xdg::Entry *entry: m_entries.getEntries())
+ {
+ if(path == "" || entry->path().startsWith(path))
+ entries.append(entry->path());
+ }
+ return entries;
+}
+
+void xdg::DesktopEntries::_dataChanged(const QString appid, const QString item, const QString value)
+{
+ if(item == XDG_CATEGORIES)
+ {
+ emit categoriesChanged(categories());
+ }
+
+ int role = -1;
+ if (item == XDG_ICON)
+ role = IconRole;
+ else if (item == XDG_NAME)
+ role = NameRole;
+ else if(item == XDG_EXEC)
+ role = ExecRole;
+ else if (item == XDG_CATEGORIES)
+ role = CategoryRole;
+ else if (item == XDG_KEYWORDS)
+ role = KeywordsRole;
+ else if (item == XDG_NODISPLAY)
+ role = NoDisplayRole;
+ else
+ qWarning() << "XdgEntries::_dataChanged: Unknwon role " << item;
+ if(role > 0)
+ {
+ for(int i = 0; i < m_entries.count(); i++)
+ {
+ const xdg::Entry *entry = m_entries.getEntry(i);
+ if(entry->appId() == appid)
+ {
+ QVector<int> r;
+ r.append(role);
+ emit dataChanged(index(i), index(i), r);
+ }
+ }
+ }
+}
diff --git a/src/lib/xdgdesktopentries.h b/src/lib/xdgdesktopentries.h
new file mode 100644
index 0000000..89aff08
--- /dev/null
+++ b/src/lib/xdgdesktopentries.h
@@ -0,0 +1,58 @@
+#ifndef XDGDESKTOPENTRIES_H
+#define XDGDESKTOPENTRIES_H
+
+#include <QAbstractListModel>
+#include <QObject>
+#include "xdgentries.h"
+
+namespace xdg {
+ class DesktopEntries : public QAbstractListModel
+ {
+ Q_OBJECT
+ Q_PROPERTY(QStringList categories READ categories NOTIFY categoriesChanged)
+ xdg::Entries m_entries;
+
+ private slots:
+ bool _contains(QString path);
+ //void _reload(QString path);
+ QStringList _getEntriesFrom(QString path = "");
+ void _dataChanged(const QString appid, const QString item, const QString value);
+
+ public:
+ explicit DesktopEntries(QObject *parent = nullptr);
+
+ enum XdgDesktopEntriesRoles {
+ PathRole = Qt::UserRole + 1,
+ NameRole,
+ IconRole,
+ CategoryRole,
+ NoDisplayRole,
+ KeywordsRole,
+ ExecRole,
+ DesktopFileRole,
+ AppIdRole,
+ AppNameRole,
+ EntryRole
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ QStringList categories();
+
+ public slots:
+ void start(QString path, const QStringList &args = QStringList());
+ void stop(QString path, const QStringList &args = QStringList());
+ QString appIdData(QString appid, QString key);
+
+ signals:
+ void raiseProcess(int pid);
+ void categoriesChanged(QStringList categories);
+
+ private:
+ QString m_sources;
+ };
+}
+
+#endif // XDGDESKTOPENTRIES_H
diff --git a/src/lib/xdgdesktopfilter.cpp b/src/lib/xdgdesktopfilter.cpp
new file mode 100644
index 0000000..78e2ca2
--- /dev/null
+++ b/src/lib/xdgdesktopfilter.cpp
@@ -0,0 +1,131 @@
+#include "xdgdesktopfilter.h"
+#include "xdgdesktopentries.h"
+#include "xdgentry.h"
+#include <qdebug.h>
+
+xdg::DesktopFilter::DesktopFilter(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+}
+
+QObject *xdg::DesktopFilter::model() const
+{
+ return static_cast<QObject*>(m_model);
+}
+
+QString xdg::DesktopFilter::category() const
+{
+ return m_category;
+}
+
+QString xdg::DesktopFilter::section() const
+{
+ return m_section;
+}
+
+
+bool xdg::DesktopFilter::showAll() const
+{
+ return m_showAll;
+}
+
+bool xdg::DesktopFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
+ QVariant ventry = sourceModel()->data(index, xdg::DesktopEntries::EntryRole);
+ const xdg::Entry *entry = qvariant_cast<const xdg::Entry *>(ventry);
+ if(!entry)
+ {
+ qWarning() << "Can't retrive entry for " << sourceModel()->data(index, xdg::DesktopEntries::NameRole).toString();
+ return false;
+ }
+
+ if(!m_section.isEmpty() && entry->data(m_section + "/Exec").isEmpty())
+ {
+ return false;
+ }
+ if(entry->data(XDG_NAME).isEmpty())
+ return false;
+ if(!m_showAll && entry->data(XDG_NODISPLAY).toLower() == "true")
+ return false;
+ if(m_category.size() > 0 && entry->data(XDG_CATEGORIES) != m_category)
+ return false;
+
+ return true;
+}
+
+
+QVariant xdg::DesktopFilter::data(const QModelIndex &index, int role) const
+{
+ if(!m_section.isEmpty())
+ {
+ const xdg::Entry *entry = QSortFilterProxyModel::data(index, xdg::DesktopEntries::EntryRole).value<const xdg::Entry *>();
+ if(entry)
+ {
+ QString res;
+ switch(role) {
+ case Qt::DisplayRole:
+ case xdg::DesktopEntries::NameRole:
+ res = entry->data(m_section + "/Name");
+ return (res.isEmpty())?(res):(entry->data(XDG_NAME));
+ case xdg::DesktopEntries::ExecRole:
+ res = entry->data(m_section + "/Exec");
+ return (!res.isEmpty())?(res):(entry->data(XDG_EXEC));
+ case xdg::DesktopEntries::IconRole:
+ res = entry->data(m_section + "/Icon");
+ return (!res.isEmpty())?(res):(entry->data(XDG_ICON));
+ case xdg::DesktopEntries::CategoryRole:
+ res = entry->data(m_section + "/Categories");
+ return (!res.isEmpty())?(res):(entry->data(XDG_CATEGORIES));
+ case xdg::DesktopEntries::KeywordsRole:
+ res = entry->data(m_section + "/Keywords");
+ return (!res.isEmpty())?(res):(entry->data(XDG_KEYWORDS));
+ }
+ }
+ }
+ return QSortFilterProxyModel::data(index, role);
+}
+
+void xdg::DesktopFilter::setModel(QObject *model)
+{
+ if (m_model == model)
+ return;
+
+ xdg::DesktopEntries *entries = qobject_cast<xdg::DesktopEntries*>(model);
+ if(entries)
+ {
+ m_model = entries;
+ setSourceModel(m_model);
+ emit modelChanged(m_model);
+ }
+}
+
+void xdg::DesktopFilter::setCategory(QString category)
+{
+ if (m_category == category)
+ return;
+
+ m_category = category;
+ invalidateFilter();
+ emit categoryChanged(m_category);
+}
+
+void xdg::DesktopFilter::setSection(QString section)
+{
+ if (m_section == section)
+ return;
+
+ m_section = section;
+ invalidateFilter();
+ emit sectionChanged(m_section);
+}
+
+void xdg::DesktopFilter::setShowAll(bool showall)
+{
+ if (m_showAll == showall)
+ return;
+
+ m_showAll = showall;
+ invalidateFilter();
+ emit showAllChanged(m_showAll);
+}
diff --git a/src/lib/xdgdesktopfilter.h b/src/lib/xdgdesktopfilter.h
new file mode 100644
index 0000000..abe2f06
--- /dev/null
+++ b/src/lib/xdgdesktopfilter.h
@@ -0,0 +1,49 @@
+#ifndef XDGDESKTOPFILTER_H
+#define XDGDESKTOPFILTER_H
+
+#include <QSortFilterProxyModel>
+#include <QObject>
+#include "xdgdesktopentries.h"
+
+namespace xdg {
+ class DesktopEntries;
+
+ class DesktopFilter : public QSortFilterProxyModel
+ {
+ Q_OBJECT
+ Q_PROPERTY(QObject *model READ model WRITE setModel NOTIFY modelChanged)
+ Q_PROPERTY(QString category READ category WRITE setCategory NOTIFY categoryChanged)
+ Q_PROPERTY(QString section READ section WRITE setSection NOTIFY sectionChanged)
+ Q_PROPERTY(bool showAll READ showAll WRITE setShowAll NOTIFY showAllChanged)
+
+ xdg::DesktopEntries *m_model;
+ QString m_category;
+ QString m_section;
+ bool m_showAll {false};
+
+ public:
+ explicit DesktopFilter(QObject *parent = nullptr);
+ QObject *model() const;
+ QString category() const;
+ QString section() const;
+ bool showAll() const;
+ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+
+ public slots:
+ void setModel(QObject * model);
+ void setCategory(QString category);
+ void setSection(QString category);
+ void setShowAll(bool showall);
+
+ signals:
+ void raise(int pid);
+ void modelChanged(QObject * model);
+ void categoryChanged(QString category);
+ void sectionChanged(QString category);
+ void showAllChanged(bool showall);
+ };
+
+}
+#endif // XDGDESKTOPFILTER_H
diff --git a/src/lib/xdgentries.cpp b/src/lib/xdgentries.cpp
new file mode 100644
index 0000000..790e472
--- /dev/null
+++ b/src/lib/xdgentries.cpp
@@ -0,0 +1,128 @@
+#include "xdgentries.h"
+#include "xdgentry.h"
+#include <QDebug>
+#include <QDir>
+#include <QFileInfo>
+#include <set>
+#include <algorithm>
+
+xdg::Entries::Entries(QObject *parent)
+ : QObject(parent)
+{
+ QObject::connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &Entries::_directoryChanged, Qt::QueuedConnection);
+}
+
+void xdg::Entries::_directoryChanged(const QString &path)
+{
+ QDir directory(path);
+ QStringList old, now, removed, added;
+ for(xdg::Entry *entry: m_entries)
+ {
+ if(entry->path().startsWith(directory.absolutePath()))
+ {
+ old.append(entry->path());
+ }
+ }
+ for(const QString &item: directory.entryList(QStringList() << "*.desktop", QDir::Files))
+ {
+ QString current = directory.absoluteFilePath(item);
+ if(current.startsWith(directory.absolutePath()))
+ {
+ now.append(current);
+ }
+ }
+ std::set_difference(old.begin(), old.end(), now.begin(), now.end(), std::inserter(removed, removed.begin()));
+ std::set_difference(now.begin(), now.end(), old.begin(), old.end(), std::inserter(added, added.begin()));
+ for(const QString &item: removed)
+ {
+ unregister(item);
+ }
+ for(const QString &item: added)
+ {
+ add(item, true);
+ }
+}
+
+void xdg::Entries::_entryDataChanged(const QString key, const QString value)
+{
+ xdg::Entry* ent = qobject_cast<xdg::Entry*>(QObject::sender());
+ emit entryDataChanged(ent->appId(), key, value);
+}
+
+void xdg::Entries::unregister(const QString &path)
+{
+ QFileInfo info(path);
+ QString id = info.completeBaseName();
+ int oldid = m_entries.keys().indexOf(id);
+ emit startRemoveEntry(oldid);
+ xdg::Entry* old = m_entries[id];
+ m_watcher.removePath(old->path());
+ m_entries.remove(id);
+ emit endRemoveEntry(oldid);
+}
+
+int xdg::Entries::add(const QString &path, bool replace)
+{
+ QFileInfo info(path);
+ QString id = info.completeBaseName();
+ if(!info.isFile() || info.suffix() != "desktop")
+ {
+ qWarning() << path << "is not a desktop entry";
+ return -1;
+ }
+
+ if(m_entries.contains(id))
+ {
+ if(replace)
+ {
+ unregister(path);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ xdg::Entry* entry = new xdg::Entry(path);
+ QObject::connect(entry, &xdg::Entry::dataChanged, this, &Entries::_entryDataChanged);
+ QObject::connect(entry, &xdg::Entry::raiseProcess, this, &Entries::raiseProcess);
+
+ m_entries[id] = entry;
+ emit startCreateEntry(m_entries.keys().indexOf(id));
+ emit endCreateEntry(m_entries.keys().indexOf(id));
+ return 0;
+}
+
+int xdg::Entries::addDirectory(const QString &path, bool replace)
+{
+ QFileInfo info(path.trimmed());
+ if(!info.isDir())
+ {
+ return -1;
+ }
+
+ QDir directory(path.trimmed());
+ QStringList items = directory.entryList(QStringList() << "*.desktop", QDir::Files);
+ for(QString &item: items)
+ {
+ add(directory.absoluteFilePath(item), replace);
+ }
+
+ m_watcher.addPath(path.trimmed());
+ return 0;
+}
+
+qsizetype xdg::Entries::count() const
+{
+ return m_entries.count();
+}
+
+const xdg::Entry *xdg::Entries::getEntry(int i) const
+{
+ return m_entries[m_entries.keys()[i]];
+}
+
+QList<xdg::Entry *> xdg::Entries::getEntries()
+{
+ return m_entries.values();
+}
diff --git a/src/lib/xdgentries.h b/src/lib/xdgentries.h
new file mode 100644
index 0000000..fd0458a
--- /dev/null
+++ b/src/lib/xdgentries.h
@@ -0,0 +1,41 @@
+#ifndef XDGENTRIES_H
+#define XDGENTRIES_H
+
+#include <QObject>
+#include <QFileSystemWatcher>
+#include <QMap>
+
+namespace xdg {
+ class Entry;
+
+ class Entries : public QObject
+ {
+ Q_OBJECT
+ QString m_type;
+ QFileSystemWatcher m_watcher;
+ QMap<QString, xdg::Entry*> m_entries;
+
+ private slots:
+ void _directoryChanged(const QString &path);
+ void _entryDataChanged(const QString key, const QString value);
+
+ public:
+ explicit Entries(QObject *parent = nullptr);
+ void unregister(const QString &path);
+ int add(const QString &path, bool replace = false);
+ int addDirectory(const QString &path, bool replace = false);
+ qsizetype count() const;
+ const Entry *getEntry(int i) const;
+ QList<xdg::Entry *> getEntries();
+
+ signals:
+ void entryDataChanged(const QString appid, const QString key, const QString value);
+ void raiseProcess(int pid);
+ void startCreateEntry(int id);
+ void endCreateEntry(int id);
+ void startRemoveEntry(int id);
+ void endRemoveEntry(int id);
+ };
+}
+
+#endif // XDGENTRIES_H
diff --git a/src/lib/xdgentry.cpp b/src/lib/xdgentry.cpp
new file mode 100644
index 0000000..83438dd
--- /dev/null
+++ b/src/lib/xdgentry.cpp
@@ -0,0 +1,315 @@
+#include "xdgentry.h"
+#include <QSettings>
+#include <qdebug.h>
+#include <signal.h>
+#include <vector>
+#include <string>
+#include <QFileInfo>
+#include "xdgbasedir.h"
+
+static inline QStringList tokenize(const QString& command)
+{
+ std::string s = command.toStdString();
+ std::vector<std::string> result;
+
+ std::string token;
+ char quote{};
+ bool escape{false};
+
+ for (char c : s)
+ {
+ if (escape)
+ {
+ escape = false;
+ if (quote && c != '\\' && c != quote)
+ token += '\\';
+ token += c;
+ }
+ else if (c == '\\')
+ {
+ escape = true;
+ }
+ else if (!quote && (c == '\'' || c == '\"'))
+ {
+ quote = c;
+ }
+ else if (quote && c == quote)
+ {
+ quote = '\0';
+ if (token.empty())
+ result.emplace_back();
+ }
+ else if (!isspace(c) || quote)
+ {
+ token += c;
+ }
+ else if (!token.empty())
+ {
+ result.push_back(std::move(token));
+ token.clear();
+ }
+ }
+
+ if (!token.empty())
+ {
+ result.push_back(std::move(token));
+ token.clear();
+ }
+
+
+ QStringList l;
+ for (auto t: result)
+ l.append(QString(t.c_str()));
+ return l;
+}
+
+static inline QStringList args_substitution(const QStringList &tokens, const QStringList &args)
+{
+ QStringList out;
+ for (auto t : tokens)
+ {
+ if (t == "%d" || t == "%D" || t == "%n" || t == "%N" || t == "%v" || t == "%m") { //deprectaed stuff
+ continue;
+ } else if (t == "%i" || t == "%c") { //unsupported stuff
+ qWarning() << "XdgEntry: %i and %c are not supported (found in " << tokens.join(" ")<<")";
+ continue;
+ } else if (t == "%f" || t == "%F") { // files
+ if (t == "%f")
+ {
+ if (args.length() > 0)
+ {
+ out.append(args[0]);
+ }
+ else
+ {
+ out.append(args);
+ }
+ }
+ } else if (t == "%u" || t == "%U") { // urls
+ // TODO : handle URLs properly
+ if (t == "%u")
+ {
+ if (args.length() > 0)
+ {
+ out.append(args[0]);
+ }
+ else
+ {
+ out.append(args);
+ }
+ }
+ } else {
+ out.append(t);
+ }
+ }
+ return out;
+}
+
+
+xdg::Entry::Entry(QString desktopfile, QObject *parent)
+ : QObject(parent)
+ , m_path(desktopfile)
+ , m_file(new QSettings(desktopfile, QSettings::IniFormat, this))
+ , m_watcher(QStringList() << desktopfile)
+{
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert("QT_QPA_PLATFORM", "wayland-egl");
+ env.remove("QT_IM_MODULE");
+ m_process.setProcessEnvironment(env);
+ QObject::connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &Entry::_desktopFileChanged, Qt::QueuedConnection);
+ QObject::connect(&m_process, &QProcess::stateChanged, this, &Entry::_processStateChanged);
+}
+
+xdg::Entry::Entry(QObject *parent)
+ : QObject(parent)
+{
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert("QT_QPA_PLATFORM", "wayland-egl");
+ env.remove("QT_IM_MODULE");
+ m_process.setProcessEnvironment(env);
+ QObject::connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &Entry::_desktopFileChanged, Qt::QueuedConnection);
+ QObject::connect(&m_process, &QProcess::stateChanged, this, &Entry::_processStateChanged);
+}
+
+xdg::Entry::Entry(const Entry &origin)
+ : QObject(origin.parent())
+ , m_path(origin.path())
+ , m_file(new QSettings(origin.path(), QSettings::IniFormat, this))
+ , m_watcher(QStringList() << origin.path())
+{
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.insert("QT_QPA_PLATFORM", "wayland-egl");
+ env.remove("QT_IM_MODULE");
+ m_process.setProcessEnvironment(env);
+ QObject::connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &Entry::_desktopFileChanged, Qt::QueuedConnection);
+ QObject::connect(&m_process, &QProcess::stateChanged, this, &Entry::_processStateChanged);
+}
+
+
+QString xdg::Entry::data(const QString &key) const
+{
+ if(!m_file)
+ return QString();
+ if(key == XDG_KEYWORDS)
+ {
+ QString keywords = m_file->value(key).toString();
+ if(keywords.contains("RemoteProcess"))
+ return keywords;
+
+ switch(m_process.state())
+ {
+ case QProcess::Running:
+ keywords.append(" running");
+ break;
+ case QProcess::NotRunning:
+ keywords.append(" stopped");
+ break;
+ case QProcess::Starting:
+ keywords.append(" starting");
+ break;
+ }
+ return keywords;
+ }
+ if(!m_used.contains(key))
+ m_used[key] = m_file->value(key).toString();
+ return m_file->value(key).toString();
+}
+
+void xdg::Entry::start(QStringList args)
+{
+ QString exec = data(XDG_EXEC);
+ QString startExec = data(XDG_ACTION_START);
+
+ if(exec.size() > 0)
+ {
+ QStringList param = tokenize(exec);
+ QString cmd = param.takeFirst();
+ QStringList argv = args_substitution(param, args);
+
+ if(m_process.state() == QProcess::Running)
+ {
+ emit raiseProcess(m_process.processId());
+ }
+ else
+ {
+ m_process.start(cmd, argv);
+ }
+ }
+ else if(startExec.size() > 0)
+ {
+ QStringList param = tokenize(startExec);
+ QString cmd = param.takeFirst();
+ QStringList argv = args_substitution(param, args);
+
+ m_process.start(cmd, argv);
+ }
+ else
+ {
+ qWarning() << "Can't find a way to start " << m_path;
+ }
+}
+
+void xdg::Entry::stop(QStringList args)
+{
+ QString stopExec = data(XDG_ACTION_STOP);
+
+ if(stopExec.size() > 0)
+ {
+ QStringList param = tokenize(stopExec);
+ QString cmd = param.takeFirst();
+ QStringList argv = args_substitution(param, args);
+
+ m_process.start(cmd, argv);
+ }
+ else
+ {
+ if(m_process.state() == QProcess::NotRunning)
+ return;
+ kill(m_process.processId(), SIGTERM);
+ }
+}
+
+QString xdg::Entry::path() const
+{
+ return m_path;
+}
+
+QString xdg::Entry::appId() const
+{
+ QFileInfo fi(path());
+ return fi.completeBaseName();
+}
+
+QString xdg::Entry::icon() const
+{
+ return data(XDG_ICON);
+}
+
+int xdg::Entry::pid() const
+{
+ return m_process.processId();
+}
+
+void xdg::Entry::setDesktopFile(QString desktopFile)
+{
+ m_path = desktopFile;
+ m_file = new QSettings(desktopFile, QSettings::IniFormat);
+ m_watcher.addPaths(QStringList() << desktopFile);
+ emit iconChanged(data(XDG_ICON));
+}
+
+void xdg::Entry::setAppId(QString appId)
+{
+ /* Search for appid destopfile */
+ QString filename;
+
+ filename = xdg::dataHome() + "/applications/" + appId + ".desktop";
+ if(filename.length())
+ {
+ QFileInfo info(filename);
+ if(info.exists())
+ {
+ setDesktopFile(filename);
+ return;
+ }
+ }
+
+ for(QString dir: xdg::dataDirs())
+ {
+ filename = dir + "/applications/" + appId + ".desktop";
+ QFileInfo info(filename);
+ if(info.exists())
+ {
+ setDesktopFile(filename);
+ return;
+ }
+ }
+ qWarning() << "Can't find Desktop Entry for " + appId;
+}
+
+
+void xdg::Entry::_desktopFileChanged(const QString &path)
+{
+ if(!m_file)
+ return;
+ QFileInfo info(path);
+ if(!info.isFile())
+ {
+ emit removed(path);
+ return;
+ }
+ m_file->sync();
+ for(const QString &key: m_used.keys())
+ {
+ if(m_file->value(key).toString() != m_used[key])
+ {
+ m_used[key] = m_file->value(key).toString();
+ emit dataChanged(key, m_used[key]);
+ }
+ }
+}
+
+void xdg::Entry::_processStateChanged(QProcess::ProcessState newState)
+{
+ emit dataChanged(XDG_KEYWORDS, data(XDG_KEYWORDS));
+}
diff --git a/src/lib/xdgentry.h b/src/lib/xdgentry.h
new file mode 100644
index 0000000..006d3ae
--- /dev/null
+++ b/src/lib/xdgentry.h
@@ -0,0 +1,67 @@
+#ifndef XDGENTRY_H
+#define XDGENTRY_H
+
+#include <QObject>
+#include <QFileSystemWatcher>
+#include <QProcess>
+#include <QSettings>
+
+#define XDG_EXEC "Desktop Entry/Exec"
+#define XDG_ICON "Desktop Entry/Icon"
+#define XDG_NAME "Desktop Entry/Name"
+#define XDG_KEYWORDS "Desktop Entry/Keywords"
+#define XDG_NODISPLAY "Desktop Entry/NoDisplay"
+#define XDG_CATEGORIES "Desktop Entry/Categories"
+
+#define XDG_ACTION_START "Desktop Action Start/Exec"
+#define XDG_ACTION_STOP "Desktop Action Stop/Exec"
+
+namespace xdg {
+ class Entry: public QObject
+ {
+ Q_OBJECT
+
+ Q_PROPERTY(QString appId READ appId WRITE setAppId NOTIFY appIdChanged)
+ Q_PROPERTY(QString icon READ icon NOTIFY iconChanged)
+ Q_PROPERTY(QString desktopFile READ path WRITE setDesktopFile NOTIFY desktopFileChanged)
+
+ QString m_path;
+ QSettings *m_file {nullptr};
+ QFileSystemWatcher m_watcher;
+ QProcess m_process;
+ mutable QMap<QString, QString> m_used;
+
+ private slots:
+ void _desktopFileChanged(const QString &path);
+ void _processStateChanged(QProcess::ProcessState newState);
+
+ public:
+ explicit Entry(QString desktopfile, QObject *parent = nullptr);
+ explicit Entry(QObject *parent = nullptr);
+ explicit Entry(const xdg::Entry& origin);
+
+ QString path() const;
+ QString appId() const;
+ QString icon() const;
+ int pid() const;
+ void setDesktopFile(QString desktopFile);
+ void setAppId(QString appId);
+
+ public slots:
+ void start(QStringList args);
+ void stop(QStringList args);
+ QString data(const QString &key) const;
+
+ signals:
+ void dataChanged(const QString key, const QString value);
+ void raiseProcess(int pid);
+ void removed(const QString path);
+ void appIdChanged(QString appId);
+ void iconChanged(QString icon);
+ void desktopFileChanged(QString desktopFile);
+ };
+}
+
+Q_DECLARE_METATYPE(const xdg::Entry*)
+
+#endif // XDGENTRY_H
diff --git a/src/lib/xdgnotificationactions.cpp b/src/lib/xdgnotificationactions.cpp
new file mode 100644
index 0000000..afa7c19
--- /dev/null
+++ b/src/lib/xdgnotificationactions.cpp
@@ -0,0 +1,63 @@
+#include "xdgnotificationactions.h"
+#include <QDebug>
+
+xdg::NotificationActions::NotificationActions(QObject *parent)
+ : QAbstractListModel(parent)
+{
+
+}
+
+QStringList xdg::NotificationActions::actionslist() const
+{
+ return m_actionslist;
+}
+
+void xdg::NotificationActions::setActionslist(const QStringList &newActionslist)
+{
+ if(newActionslist.count() % 2 != 0)
+ {
+ qWarning() << "Error in actionlist:" << newActionslist;
+ }
+
+ if (m_actionslist == newActionslist)
+ return;
+ m_actionslist = newActionslist;
+
+ beginResetModel();
+ m_actions.clear();
+ for(int i = 0; i < newActionslist.count() / 2; i++)
+ {
+ m_actions.append(QPair<QString, QString>(newActionslist[i * 2], newActionslist[(i * 2) + 1]));
+ }
+ endResetModel();
+
+ emit actionslistChanged();
+}
+
+int xdg::NotificationActions::rowCount(const QModelIndex &parent) const
+{
+ return m_actions.count();
+}
+
+QVariant xdg::NotificationActions::data(const QModelIndex &index, int role) const
+{
+ if(index.row() > m_actions.count())
+ return QVariant();
+
+ switch(role) {
+ case Qt::DisplayRole:
+ case TextRole:
+ return m_actions[index.row()].second;
+ case KeyRole:
+ return m_actions[index.row()].first;
+ }
+ return QVariant();
+}
+
+QHash<int, QByteArray> xdg::NotificationActions::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
+ roles[KeyRole] = "key";
+ roles[TextRole] = "actionText";
+ return roles;
+}
diff --git a/src/lib/xdgnotificationactions.h b/src/lib/xdgnotificationactions.h
new file mode 100644
index 0000000..1d1e449
--- /dev/null
+++ b/src/lib/xdgnotificationactions.h
@@ -0,0 +1,40 @@
+#ifndef XDGNOTIFICATIONACTIONS_H
+#define XDGNOTIFICATIONACTIONS_H
+
+#include <QAbstractListModel>
+
+namespace xdg {
+
+ class NotificationActions: public QAbstractListModel
+ {
+ Q_OBJECT
+
+ QList<QPair<QString, QString>> m_actions;
+
+ Q_PROPERTY(QStringList actionslist READ actionslist WRITE setActionslist NOTIFY actionslistChanged)
+
+ public:
+ NotificationActions(QObject *parent = nullptr);
+
+ enum NotificationActionsRoles {
+ KeyRole = Qt::UserRole + 1,
+ TextRole,
+ };
+
+ QStringList actionslist() const;
+ void setActionslist(const QStringList &newActionslist);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ signals:
+ void actionslistChanged();
+ private:
+ QStringList m_actionslist;
+ };
+
+}
+
+
+#endif // XDGNOTIFICATIONACTIONS_H
diff --git a/src/lib/xdgnotificationmanager.cpp b/src/lib/xdgnotificationmanager.cpp
new file mode 100644
index 0000000..06e8594
--- /dev/null
+++ b/src/lib/xdgnotificationmanager.cpp
@@ -0,0 +1,173 @@
+#include "xdgnotificationmanager.h"
+#include "xdgnotificationserver.h"
+#include "xdgnotificationmessage.h"
+#include "notificationsadaptor.h"
+
+uint xdg::serverNotify(QObject *obj, const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
+{
+ xdg::NotificationManager *manager = qobject_cast<xdg::NotificationManager*>(obj);
+ if(replaces_id != 0 && manager->m_pending.contains(replaces_id))
+ {
+ xdg::NotificationMessage *message = manager->m_pending[replaces_id];
+ message->update(app_name, app_icon, summary, body, actions, hints, timeout);
+ }
+ else
+ {
+ xdg::NotificationMessage *message = new xdg::NotificationMessage(app_name, app_icon, summary, body, actions, hints, timeout, manager);
+
+ QObject::connect(message, &xdg::NotificationMessage::appNameChanged, [manager, replaces_id](){
+ const QVector<int> roles = {xdg::NotificationManager::AppNameRole};
+ emit manager->dataChanged(
+ manager->index(replaces_id),
+ manager->index(replaces_id),
+ roles);
+ });
+
+ QObject::connect(message, &xdg::NotificationMessage::appIconChanged, [manager, replaces_id](){
+ const QVector<int> roles = {xdg::NotificationManager::AppIconRole};
+ emit manager->dataChanged(
+ manager->index(replaces_id),
+ manager->index(replaces_id),
+ roles);
+ });
+
+ QObject::connect(message, &xdg::NotificationMessage::summaryChanged, [manager, replaces_id](){
+ const QVector<int> roles = {xdg::NotificationManager::SummaryRole};
+ emit manager->dataChanged(
+ manager->index(replaces_id),
+ manager->index(replaces_id),
+ roles);
+ });
+
+ QObject::connect(message, &xdg::NotificationMessage::bodyChanged, [manager, replaces_id](){
+ const QVector<int> roles = {xdg::NotificationManager::BodyRole};
+ emit manager->dataChanged(
+ manager->index(replaces_id),
+ manager->index(replaces_id),
+ roles);
+ });
+
+ QObject::connect(message, &xdg::NotificationMessage::actionsChanged, [manager, replaces_id](){
+ const QVector<int> roles = {xdg::NotificationManager::ActionsRole};
+ emit manager->dataChanged(
+ manager->index(replaces_id),
+ manager->index(replaces_id),
+ roles);
+ });
+
+ QObject::connect(message, &xdg::NotificationMessage::hintsChanged, [manager, replaces_id](){
+ const QVector<int> roles = {xdg::NotificationManager::HintsRole};
+ emit manager->dataChanged(
+ manager->index(replaces_id),
+ manager->index(replaces_id),
+ roles);
+ });
+
+
+ QObject::connect(message, &xdg::NotificationMessage::expired, [manager](QObject *obj){
+ xdg::NotificationMessage *msg = qobject_cast<xdg::NotificationMessage*>(obj);
+ if(msg)
+ {
+ manager->close(manager->m_pending.key(msg), 1);
+ }
+ });
+
+ manager->beginInsertRows(QModelIndex(), 0, 0);
+ manager->m_pending[++manager->m_last] = message;
+ message->setId(manager->m_last);
+ manager->m_ids.insert(0, manager->m_last);
+ manager->endInsertRows();
+ }
+ return manager->m_last;
+}
+
+
+xdg::NotificationManager::NotificationManager(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ m_server = new XdgNotificationServer(this);
+ m_server->setNotify(serverNotify, this);
+ QObject::connect(m_server, &XdgNotificationServer::closeNotification, this, &NotificationManager::_closeNotification);
+ QObject::connect(this, &NotificationManager::rowsRemoved, this, &NotificationManager::notificationsChanged);
+ QObject::connect(this, &NotificationManager::rowsInserted, this, &NotificationManager::notificationsChanged);
+ new NotificationsAdaptor(m_server);
+ XdgNotificationServer::RegisterServerToDbus(*m_server);
+}
+
+int xdg::NotificationManager::rowCount(const QModelIndex &parent) const
+{
+ return m_ids.count();
+}
+
+QVariant xdg::NotificationManager::data(const QModelIndex &index, int role) const
+{
+ if(index.row() > m_ids.count())
+ return QVariant();
+
+ const NotificationMessage *message = m_pending[m_ids[index.row()]];
+ if(message == nullptr)
+ return QVariant();
+
+ switch(role) {
+ case Qt::DisplayRole:
+ case AppNameRole:
+ return message->appName();
+ case AppIconRole:
+ return message->appIcon();
+ case SummaryRole:
+ return message->summary();
+ case BodyRole:
+ return message->body();
+ case ActionsRole:
+ return message->actions();
+ case HintsRole:
+ return message->hints();
+ case IdRole:
+ return message->id();
+ }
+ return QVariant();
+}
+
+QHash<int, QByteArray> xdg::NotificationManager::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
+ roles[AppNameRole] = "appName";
+ roles[AppIconRole] = "appIcon";
+ roles[SummaryRole] = "summary";
+ roles[BodyRole] = "body";
+ roles[ActionsRole] = "actions";
+ roles[HintsRole] = "hints";
+ roles[IdRole] = "appId";
+ return roles;
+}
+
+
+int xdg::NotificationManager::nbNotifications()
+{
+ return rowCount();
+}
+
+void xdg::NotificationManager::close(unsigned int id, unsigned int reason)
+{
+ unsigned int index = m_ids.indexOf(id);
+ beginRemoveRows(QModelIndex(), index, index);
+ m_ids.removeAll(id);
+ NotificationMessage *message = m_pending[id];
+ m_pending.remove(id);
+ if (message != NULL) {
+ message->deleteLater();
+ }
+ endRemoveRows();
+ m_server->sendNotificationClosed(id, reason);
+}
+
+void xdg::NotificationManager::returnAction(unsigned int appid, const QString &key)
+{
+ m_server->sendActionInvoked(appid, key);
+ close(appid, 2);
+}
+
+void xdg::NotificationManager::_closeNotification(unsigned int id)
+{
+ close(id, 3);
+}
diff --git a/src/lib/xdgnotificationmanager.h b/src/lib/xdgnotificationmanager.h
new file mode 100644
index 0000000..181bf0b
--- /dev/null
+++ b/src/lib/xdgnotificationmanager.h
@@ -0,0 +1,55 @@
+#ifndef SystrayNotifyManager_H
+#define SystrayNotifyManager_H
+
+#include <QAbstractListModel>
+#include <QObject>
+
+class XdgNotificationServer;
+
+namespace xdg {
+ uint serverNotify(QObject *manager, const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout);
+ class NotificationMessage;
+
+ class NotificationManager : public QAbstractListModel
+ {
+ Q_OBJECT
+ Q_PROPERTY(int nbNotifications READ nbNotifications NOTIFY notificationsChanged)
+
+ XdgNotificationServer *m_server;
+ unsigned int m_last = 0;
+ QMap<unsigned int, NotificationMessage *> m_pending;
+ QList<unsigned int> m_ids;
+
+ friend uint serverNotify(QObject *manager, const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout);
+ public:
+ explicit NotificationManager(QObject *parent = nullptr);
+
+ enum SystrayNotifyRoles {
+ AppNameRole = Qt::UserRole + 1,
+ AppIconRole,
+ SummaryRole,
+ BodyRole,
+ ActionsRole,
+ HintsRole,
+ IdRole,
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+
+ public slots:
+ int nbNotifications();
+ void close(unsigned int id, unsigned int reason = 2);
+ void returnAction(unsigned int appid, const QString &key);
+
+ private slots:
+ void _closeNotification(unsigned int id);
+
+ signals:
+ void notificationsChanged();
+ };
+}
+
+#endif // SystrayNotifyManager_H
diff --git a/src/lib/xdgnotificationmessage.cpp b/src/lib/xdgnotificationmessage.cpp
new file mode 100644
index 0000000..3dc0e2e
--- /dev/null
+++ b/src/lib/xdgnotificationmessage.cpp
@@ -0,0 +1,119 @@
+#include "xdgnotificationmessage.h"
+#include <QTimer>
+
+
+QString xdg::NotificationMessage::appName() const
+{
+ return m_appName;
+}
+
+QString xdg::NotificationMessage::appIcon() const
+{
+ return m_appIcon;
+}
+
+QString xdg::NotificationMessage::summary() const
+{
+ return m_summary;
+}
+
+QString xdg::NotificationMessage::body() const
+{
+ return m_body;
+}
+
+QStringList xdg::NotificationMessage::actions() const
+{
+ return m_actions;
+}
+
+QVariantMap xdg::NotificationMessage::hints() const
+{
+ return m_hints;
+}
+
+uint xdg::NotificationMessage::id() const
+{
+ return m_id;
+}
+
+void xdg::NotificationMessage::_expired()
+{
+ emit expired(this);
+}
+
+
+xdg::NotificationMessage::NotificationMessage(
+ const QString &app_name,
+ const QString &app_icon,
+ const QString &summary,
+ const QString &body,
+ const QStringList &actions,
+ const QVariantMap &hints,
+ int timeout,
+ QObject *parent)
+ : QObject(parent)
+ , m_appName(app_name)
+ , m_appIcon(app_icon)
+ , m_summary(summary)
+ , m_body(body)
+ , m_actions(actions)
+ , m_hints(hints)
+ , m_timeout(timeout)
+{
+ if(m_timeout > 0)
+ QTimer::singleShot(m_timeout, this, &xdg::NotificationMessage::_expired);
+}
+
+void xdg::NotificationMessage::update(const QString &app_name, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
+{
+ if(m_appName != app_name)
+ {
+ m_appName = app_name;
+ emit appNameChanged(m_appName);
+ }
+
+ if(m_appIcon != app_icon)
+ {
+ m_appIcon = app_icon;
+ emit appIconChanged(m_appIcon);
+ }
+
+ if(m_summary != summary)
+ {
+ m_summary = summary;
+ emit summaryChanged(m_summary);
+ }
+
+ if(m_body != app_name)
+ {
+ m_body = body;
+ emit bodyChanged(m_body);
+ }
+
+ if(m_actions != actions)
+ {
+ m_actions = actions;
+ emit actionsChanged(m_actions);
+ }
+
+ if(m_hints != hints)
+ {
+ m_hints = hints;
+ emit hintsChanged(m_hints);
+ }
+
+ if(m_timeout != timeout)
+ {
+ m_timeout = timeout;
+ emit timeoutChanged(m_timeout);
+ }
+ if(m_timeout > 0)
+ QTimer::singleShot(m_timeout, this, &xdg::NotificationMessage::_expired);
+
+}
+
+void xdg::NotificationMessage::setId(uint id)
+{
+ m_id = id;
+}
diff --git a/src/lib/xdgnotificationmessage.h b/src/lib/xdgnotificationmessage.h
new file mode 100644
index 0000000..8e62cab
--- /dev/null
+++ b/src/lib/xdgnotificationmessage.h
@@ -0,0 +1,67 @@
+#ifndef NOTIFYMESSAGE_H
+#define NOTIFYMESSAGE_H
+
+#include <QObject>
+#include <QVariantMap>
+
+namespace xdg {
+ class NotificationMessage : public QObject
+ {
+ Q_OBJECT
+
+ QString m_appName;
+ QString m_appIcon;
+ QString m_summary;
+ QString m_body;
+ QStringList m_actions;
+ QVariantMap m_hints;
+ uint m_id;
+ int m_timeout;
+
+
+ public:
+ explicit NotificationMessage(
+ const QString &app_name,
+ const QString &app_icon,
+ const QString &summary,
+ const QString &body,
+ const QStringList &actions,
+ const QVariantMap &hints,
+ int timeout,
+ QObject *parent = nullptr);
+
+ void update(
+ const QString &app_name,
+ const QString &app_icon,
+ const QString &summary,
+ const QString &body,
+ const QStringList &actions,
+ const QVariantMap &hints,
+ int timeout);
+
+ void setId(uint id);
+
+ QString appName() const;
+ QString appIcon() const;
+ QString summary() const;
+ QString body() const;
+ QStringList actions() const;
+ QVariantMap hints() const;
+ uint id() const;
+
+ private slots:
+ void _expired();
+
+ signals:
+ void appNameChanged(QString appName);
+ void appIconChanged(QString appIcon);
+ void summaryChanged(QString summary);
+ void bodyChanged(QString body);
+ void actionsChanged(QStringList actions);
+ void hintsChanged(QVariantMap hints);
+ void timeoutChanged(int timeout);
+ void expired(QObject *obj);
+ };
+}
+
+#endif // NOTIFYMESSAGE_H
diff --git a/src/lib/xdgnotificationserver.cpp b/src/lib/xdgnotificationserver.cpp
new file mode 100644
index 0000000..3f83cea
--- /dev/null
+++ b/src/lib/xdgnotificationserver.cpp
@@ -0,0 +1,87 @@
+#include "xdgnotificationserver.h"
+#include <QDBusConnection>
+#include <QDebug>
+
+void XdgNotificationServer::setNotify(NotifyCallback newNotify, QObject *obj)
+{
+ m_notify = newNotify;
+ m_notifyData = obj;
+}
+
+void XdgNotificationServer::sendActionInvoked(uint id, const QString &action_key)
+{
+ emit ActionInvoked(id, action_key);
+}
+
+void XdgNotificationServer::sendNotificationClosed(uint id, uint reason)
+{
+ emit NotificationClosed(id, reason);
+}
+
+XdgNotificationServer::XdgNotificationServer(QObject *parent)
+ : QObject{parent}
+{
+
+}
+
+void XdgNotificationServer::RegisterServerToDbus(XdgNotificationServer &server)
+{
+ const QString service = "org.freedesktop.Notifications";
+ const QString path = "/org/freedesktop/Notifications";
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ if (!bus.isConnected()) {
+ qWarning("Cannot connect to the D-Bus session bus.\n"
+ "Please check your system settings and try again.\n");
+ }
+ bus.registerObject(path, &server);
+
+ bool conres = bus.registerService(service);
+ if(!conres) qWarning() << "Can't register dbus service " << service;
+}
+
+void XdgNotificationServer::CloseNotification(uint id)
+{
+ emit closeNotification(id);
+}
+
+QStringList XdgNotificationServer::GetCapabilities()
+{
+ return QStringList();
+}
+
+QString XdgNotificationServer::GetServerInformation(QString &vendor, QString &version, QString &spec_version)
+{
+ vendor = "KaZoe";
+ version = "1.0.0";
+ spec_version = "1.2";
+ return QString(); // Why return string ? spec is return void
+}
+
+uint XdgNotificationServer::Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
+{
+ if(m_notify)
+ {
+ return m_notify(m_notifyData, app_name, replaces_id, app_icon, summary, body, actions, hints, timeout);
+ }
+ else
+ {
+ qDebug() << "XdgNotificationServer::Notify("
+ << app_name
+ << ", "
+ << replaces_id
+ << ", "
+ << app_icon
+ << ", "
+ << summary
+ << ", "
+ << body
+ << ", "
+ << actions
+ << ", "
+ << hints
+ << ", "
+ << timeout
+ << ")";
+ }
+ return 0;
+}
diff --git a/src/lib/xdgnotificationserver.h b/src/lib/xdgnotificationserver.h
new file mode 100644
index 0000000..5bdfbdf
--- /dev/null
+++ b/src/lib/xdgnotificationserver.h
@@ -0,0 +1,42 @@
+#ifndef XDGNOTIFICATIONSERVER_H
+#define XDGNOTIFICATIONSERVER_H
+
+#include <QObject>
+
+typedef uint (*NotifyCallback)(QObject *obj, const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout) override;
+
+
+class XdgNotificationServer : public QObject
+{
+ Q_OBJECT
+
+ NotifyCallback m_notify;
+ QObject *m_notifyData;
+
+public:
+ explicit XdgNotificationServer(QObject *parent = nullptr);
+ static void RegisterServerToDbus(XdgNotificationServer &server);
+
+ void setNotify(NotifyCallback newNotify, QObject *obj);
+ void sendActionInvoked(uint id, const QString &action_key);
+ void sendNotificationClosed(uint id, uint reason);
+
+public slots:
+
+ /* DBus API */
+ virtual void CloseNotification(uint id);
+ virtual QStringList GetCapabilities();
+ virtual QString GetServerInformation(QString &vendor, QString &version, QString &spec_version);
+ virtual uint Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout);
+
+
+
+signals:
+ void closeNotification(uint id);
+
+ void ActionInvoked(uint id, const QString &action_key);
+ void NotificationClosed(uint id, uint reason);
+
+};
+
+#endif // XDGNOTIFICATIONSERVER_H
diff --git a/src/lib/xdgnotifier.cpp b/src/lib/xdgnotifier.cpp
new file mode 100644
index 0000000..da52ec5
--- /dev/null
+++ b/src/lib/xdgnotifier.cpp
@@ -0,0 +1,99 @@
+#include "xdgnotifier.h"
+#include "NotificationsIface.h"
+#include <QGuiApplication>
+
+XdgNotifier::XdgNotifier(QObject *parent)
+ : QObject{parent}
+{
+ QDBusConnection connection = QDBusConnection::sessionBus();
+
+ if (!connection.isConnected()) {
+ qWarning("Cannot connect to the D-Bus session bus.\n"
+ "Please check your system settings and try again.\n");
+ }
+ const QString service = "org.freedesktop.Notifications";
+ const QString path = "/org/freedesktop/Notifications";
+
+ m_iface = new OrgFreedesktopNotificationsInterface(service, path, connection);
+
+ QObject::connect(m_iface, &OrgFreedesktopNotificationsInterface::ActionInvoked, [this](uint id, const QString &action_key){
+ qDebug() << "Recive signal ActionInvoked(" << id << ", " << action_key << ")";
+ emit actionInvoked(action_key);
+ });
+
+ QObject::connect(m_iface, &OrgFreedesktopNotificationsInterface::NotificationClosed, [this](uint id, uint reason){
+ qDebug() << "Recive signal NotificationClosed(" << id << ", " << reason << ")";
+ switch(reason)
+ {
+ case 1:
+ emit notificationClosed("The notification expired.");
+ break;
+ case 2:
+ emit notificationClosed("The notification was dismissed by the user.");
+ break;
+ case 3:
+ emit notificationClosed(" The notification was closed by a call to CloseNotification.");
+ break;
+ default:
+ emit notificationClosed("Undefined/reserved reasons.");
+ break;
+ }
+ });
+}
+
+void XdgNotifier::notifyTextOnly(QString summary, QString body, bool replace)
+{
+ qDebug() << "notifyTextOnly(" << summary << ", " << body << ")";
+ auto reply = m_iface->Notify(
+ QGuiApplication::applicationDisplayName(),
+ (replace)?(m_lastid):(0),
+ QString(),
+ summary,
+ body,
+ QStringList(),
+ QVariantMap(),
+ -1
+ );
+ reply.waitForFinished();
+ if(!replace)
+ m_lastid = reply.value();
+}
+
+void XdgNotifier::notifyWithActions(QString summary, QString body, bool replace, QStringList actions)
+{
+ auto reply = m_iface->Notify(
+ QGuiApplication::applicationDisplayName(),
+ (replace)?(m_lastid):(0),
+ QString(),
+ summary,
+ body,
+ actions,
+ QVariantMap(),
+ -1
+ );
+ reply.waitForFinished();
+ if(!replace)
+ m_lastid = reply.value();
+}
+
+void XdgNotifier::notify(const QString &summary, const QString &body, bool replace, const QStringList &actions, int timeout)
+{
+ auto reply = m_iface->Notify(
+ QGuiApplication::applicationDisplayName(),
+ (replace)?(m_lastid):(0),
+ QString(),
+ summary,
+ body,
+ actions,
+ QVariantMap(),
+ timeout
+ );
+ reply.waitForFinished();
+ if(!replace)
+ m_lastid = reply.value();
+}
+
+void XdgNotifier::closeNotification()
+{
+ auto reply = m_iface->CloseNotification(m_lastid);
+}
diff --git a/src/lib/xdgnotifier.h b/src/lib/xdgnotifier.h
new file mode 100644
index 0000000..7147626
--- /dev/null
+++ b/src/lib/xdgnotifier.h
@@ -0,0 +1,28 @@
+#ifndef NOTIFIER_H
+#define NOTIFIER_H
+
+#include <QObject>
+#include <QDBusConnection>
+
+class OrgFreedesktopNotificationsInterface;
+
+class XdgNotifier : public QObject
+{
+ Q_OBJECT
+ OrgFreedesktopNotificationsInterface *m_iface;
+ int m_lastid;
+public:
+ explicit XdgNotifier(QObject *parent = nullptr);
+
+public slots:
+ void notifyTextOnly(QString summary, QString body, bool replace);
+ void notifyWithActions(QString summary, QString body, bool replace, QStringList actions);
+ void notify(const QString &summary, const QString &body, bool replace, const QStringList &actions, int timeout);
+ void closeNotification();
+
+signals:
+ void actionInvoked(QString action);
+ void notificationClosed(QString reason);
+};
+
+#endif // NOTIFIER_H
diff --git a/src/lib/xdgstatusnotifierhost.cpp b/src/lib/xdgstatusnotifierhost.cpp
new file mode 100644
index 0000000..49a17b1
--- /dev/null
+++ b/src/lib/xdgstatusnotifierhost.cpp
@@ -0,0 +1,187 @@
+#include "xdgstatusnotifierhost.h"
+#include "XdgStatusNotifierItemIface.h"
+#include "XdgStatusNotifierWatcherIface.h"
+#include <QDBusConnection>
+#include "debug.h"
+
+using namespace org::kde;
+
+namespace xdg {
+ class StatusNotifierHostPrivate {
+ Q_DISABLE_COPY(StatusNotifierHostPrivate)
+ Q_DECLARE_PUBLIC(StatusNotifierHost)
+ StatusNotifierHost * const q_ptr;
+ StatusNotifierHostPrivate(StatusNotifierHost *parent)
+ : q_ptr(parent)
+ , m_bus(QDBusConnection::sessionBus())
+ {};
+
+ QMap<QString, StatusNotifierItem*> m_items;
+ QList<QString> m_itemsorder;
+ StatusNotifierWatcher *m_watcher;
+ QDBusConnection m_bus;
+ QString m_serviceName;
+
+ void addNotifier(QString item)
+ {
+ StatusNotifierItem *stat = new StatusNotifierItem(item.split("/").first(), "/StatusNotifierItem", m_bus, q_ptr);
+ int index = m_itemsorder.size();
+ q_ptr->beginInsertRows(QModelIndex(), index, index);
+ m_itemsorder.append(item);
+ m_items[item] = stat;
+
+ QObject::connect(stat, &StatusNotifierItem::NewAttentionIcon, [index, this](){
+ QVector<int> r;
+ r.append(StatusNotifierHost::AttentionIconNameRole);
+ emit q_ptr->dataChanged(q_ptr->index(index), q_ptr->index(index), r);
+ });
+
+ QObject::connect(stat, &StatusNotifierItem::NewIcon, [index, this](){
+ QVector<int> r;
+ r.append(StatusNotifierHost::IconNameRole);
+ emit q_ptr->dataChanged(q_ptr->index(index), q_ptr->index(index), r);
+ });
+
+ QObject::connect(stat, &StatusNotifierItem::NewOverlayIcon, [index, this](){
+ QVector<int> r;
+ r.append(StatusNotifierHost::OverlayIconNameRole);
+ emit q_ptr->dataChanged(q_ptr->index(index), q_ptr->index(index), r);
+ });
+
+ QObject::connect(stat, &StatusNotifierItem::NewStatus, [index, this](){
+ QVector<int> r;
+ r.append(StatusNotifierHost::StatusRole);
+ emit q_ptr->dataChanged(q_ptr->index(index), q_ptr->index(index), r);
+ });
+
+ QObject::connect(stat, &StatusNotifierItem::NewTitle, [index, this](){
+ QVector<int> r;
+ r.append(StatusNotifierHost::TitleRole);
+ emit q_ptr->dataChanged(q_ptr->index(index), q_ptr->index(index), r);
+ });
+
+ q_ptr->endInsertRows();
+ }
+
+ void removeNotifier(QString item)
+ {
+ int index = m_itemsorder.indexOf(item);
+ q_ptr->beginRemoveRows(QModelIndex(), index, index);
+ m_itemsorder.removeAll(item);
+ m_items[item]->deleteLater();
+ m_items.remove(item);
+ q_ptr->endRemoveRows();
+ }
+ };
+};
+
+using namespace xdg;
+
+StatusNotifierHost::StatusNotifierHost(QObject *parent)
+ : QAbstractListModel{parent}
+ , d_ptr(new StatusNotifierHostPrivate(this))
+{
+ Q_D(StatusNotifierHost);
+
+ d->m_serviceName = "org.kde.StatusNotifierHost-";
+ d->m_serviceName.append(QString::number(QCoreApplication::applicationPid()));
+ qCDebug(SYSTEM_TRAY) << "Register Host service" << d->m_serviceName << ":" << d->m_bus.registerService(d->m_serviceName);
+
+ d->m_watcher = new StatusNotifierWatcher("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", d->m_bus, this);
+ qCDebug(SYSTEM_TRAY) << "Find Watcher " << d->m_watcher->isValid() << " with protocol " << d->m_watcher->protocolVersion();
+
+ if(!d->m_watcher->isValid())
+ {
+ qCWarning(SYSTEM_TRAY) << "Can't find watcher org.kde.StatusNotifierWatcher/StatusNotifierWatcher";
+ }
+
+
+ d->m_watcher->RegisterStatusNotifierHost(d->m_serviceName);
+
+ QObject::connect(d->m_watcher, &StatusNotifierWatcher::StatusNotifierItemRegistered, [this](QString in0){
+ Q_D(StatusNotifierHost);
+ d->addNotifier(in0);
+ });
+
+ QObject::connect(d->m_watcher, &StatusNotifierWatcher::StatusNotifierItemUnregistered, [this](QString in0){
+ Q_D(StatusNotifierHost);
+ d->removeNotifier(in0);
+ });
+
+ for(QString item: d->m_watcher->registeredStatusNotifierItems())
+ {
+ d->addNotifier(item);
+ }
+}
+
+StatusNotifierHost::~StatusNotifierHost() = default;
+
+int StatusNotifierHost::rowCount(const QModelIndex &parent) const
+{
+ Q_D(const StatusNotifierHost);
+ return d->m_items.size();
+}
+
+QVariant StatusNotifierHost::data(const QModelIndex &index, int role) const
+{
+ Q_D(const StatusNotifierHost);
+
+ if(index.row() > d->m_itemsorder.count())
+ return QVariant();
+
+ const StatusNotifierItem *stat = d->m_items[d->m_itemsorder[index.row()]];
+
+ switch(role) {
+ case CategoryRole:
+ return stat->category();
+ case IdRole:
+ return stat->id();
+ case TitleRole:
+ return stat->title();
+ case StatusRole:
+ return stat->status();
+ case WindowIdRole:
+ return stat->windowId();
+ case IconNameRole:
+ return stat->iconName();
+ case OverlayIconNameRole:
+ return stat->overlayIconName();
+ case AttentionIconNameRole:
+ return stat->attentionIconName();
+ case AttentionMovieNameRole:
+ return stat->attentionMovieName();
+ }
+
+ return QVariant();
+}
+
+QHash<int, QByteArray> StatusNotifierHost::roleNames() const
+{
+ QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
+ roles[CategoryRole] = "category";
+ roles[IdRole] = "idItem";
+ roles[TitleRole] = "title";
+ roles[StatusRole] = "status";
+ roles[WindowIdRole] = "windowId";
+ roles[IconNameRole] = "iconName";
+ roles[OverlayIconNameRole] = "overlayIconName";
+ roles[AttentionIconNameRole] = "attentionIconName";
+ roles[AttentionMovieNameRole] = "attentionMovieName";
+ return roles;
+}
+
+void StatusNotifierHost::activate(int x, int y, QString id, int mode)
+{
+ Q_D(const StatusNotifierHost);
+ for(const QString &key: d->m_itemsorder)
+ {
+ if(d->m_items[key]->id() == id)
+ {
+ if(mode == 1)
+ d->m_items[key]->SecondaryActivate(x, y);
+ else
+ d->m_items[key]->Activate(x, y);
+ return;
+ }
+ }
+}
diff --git a/src/lib/xdgstatusnotifierhost.h b/src/lib/xdgstatusnotifierhost.h
new file mode 100644
index 0000000..20c36ed
--- /dev/null
+++ b/src/lib/xdgstatusnotifierhost.h
@@ -0,0 +1,44 @@
+#ifndef XDGSTATUSNOTIFIERHOST_H
+#define XDGSTATUSNOTIFIERHOST_H
+
+#include <QAbstractListModel>
+#include <QObject>
+#include <QScopedPointer>
+
+namespace xdg {
+ class StatusNotifierHostPrivate;
+
+ class StatusNotifierHost : public QAbstractListModel
+ {
+ Q_OBJECT
+ public:
+ explicit StatusNotifierHost(QObject *parent = nullptr);
+ virtual ~StatusNotifierHost();
+
+ enum XdgDesktopEntriesRoles {
+ CategoryRole = Qt::UserRole + 1,
+ IdRole,
+ TitleRole,
+ StatusRole,
+ WindowIdRole,
+ IconNameRole,
+ OverlayIconNameRole,
+ AttentionIconNameRole,
+ AttentionMovieNameRole
+ };
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ public slots:
+ void activate(int x, int y, QString id, int mode);
+
+ private:
+ Q_DISABLE_COPY(StatusNotifierHost)
+ Q_DECLARE_PRIVATE(StatusNotifierHost)
+ QScopedPointer<StatusNotifierHostPrivate> const d_ptr;
+ };
+};
+
+#endif // XDGSTATUSNOTIFIERHOST_H
diff --git a/src/lib/xdgstatusnotifieritem.cpp b/src/lib/xdgstatusnotifieritem.cpp
new file mode 100644
index 0000000..1a14846
--- /dev/null
+++ b/src/lib/xdgstatusnotifieritem.cpp
@@ -0,0 +1,169 @@
+#include "xdgstatusnotifieritem.h"
+#include <statusnotifieritemadaptor.h>
+#include <XdgStatusNotifierWatcherIface.h>
+#include <QDBusConnection>
+#include <QGuiApplication>
+#include <debug.h>
+
+using namespace xdg;
+
+StatusNotifierItem::StatusNotifierItem(QObject *parent)
+ : QObject{parent}
+{
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ if (!bus.isConnected()) {
+ qCWarning(SYSTEM_TRAY) << "Cannot connect to the D-Bus session bus.\n" << "Please check your system settings and try again.\n";
+ }
+ QString serviceName = "org.xdgutils.";
+ setId((QGuiApplication::desktopFileName().isEmpty())?(QCoreApplication::applicationName()):(QGuiApplication::desktopFileName()));
+ serviceName.append(Id());
+ serviceName.append("-");
+ serviceName.append(QString::number(QCoreApplication::applicationPid()));
+ qCDebug(SYSTEM_TRAY) << "Register Item service" << serviceName << ":" << bus.registerService(serviceName);
+ new StatusNotifierItemAdaptor(this);
+ bus.registerObject("/StatusNotifierItem", this);
+ OrgKdeStatusNotifierWatcherInterface *watcher = new OrgKdeStatusNotifierWatcherInterface("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher", bus);
+ watcher->RegisterStatusNotifierItem(serviceName);
+}
+
+QString StatusNotifierItem::Title() const
+{
+ return m_Title;
+}
+
+void StatusNotifierItem::setTitle(const QString &newTitle)
+{
+ if (m_Title == newTitle)
+ return;
+ m_Title = newTitle;
+ emit NewTitle();
+ emit TitleChanged();
+}
+
+QString StatusNotifierItem::IconName() const
+{
+ return m_IconName;
+}
+
+void StatusNotifierItem::setIconName(const QString &newIconName)
+{
+ if (m_IconName == newIconName)
+ return;
+ m_IconName = newIconName;
+ emit IconNameChanged();
+ emit NewIcon();
+}
+
+
+void StatusNotifierItem::Activate(uint x, uint y)
+{
+ emit activated(x, y);
+}
+
+void StatusNotifierItem::SecondaryActivate(uint x, uint y)
+{
+ emit secondaryActivated(x, y);
+}
+
+QString StatusNotifierItem::Status() const
+{
+ return m_Status;
+}
+
+void StatusNotifierItem::setStatus(const QString &newStatus)
+{
+ if (m_Status == newStatus)
+ return;
+ if(!xdgStatus.contains(newStatus))
+ {
+ qCWarning(SYSTEM_TRAY) << "Status " << newStatus << "not authorized";
+ return;
+ }
+ m_Status = newStatus;
+ emit StatusChanged();
+ emit NewStatus(m_Status);
+}
+
+QString StatusNotifierItem::OverlayIconName() const
+{
+ return m_OverlayIconName;
+}
+
+void StatusNotifierItem::setOverlayIconName(const QString &newOverlayIconName)
+{
+ if (m_OverlayIconName == newOverlayIconName)
+ return;
+ m_OverlayIconName = newOverlayIconName;
+ emit overlayIconNameChanged();
+ emit NewOverlayIcon();
+}
+
+QString StatusNotifierItem::Category() const
+{
+ return m_Category;
+}
+
+void StatusNotifierItem::setCategory(const QString &newCategory)
+{
+ if (m_Category == newCategory)
+ return;
+ if(!xdgCategories.contains(newCategory))
+ {
+ qCWarning(SYSTEM_TRAY) << "Category " << newCategory << "not authorized";
+ return;
+ }
+ m_Category = newCategory;
+ emit CategoryChanged();
+}
+
+QString StatusNotifierItem::Id() const
+{
+ return m_Id;
+}
+
+void StatusNotifierItem::setId(const QString &newId)
+{
+ if (m_Id == newId)
+ return;
+ m_Id = newId;
+ emit IdChanged();
+}
+
+QString StatusNotifierItem::WindowId() const
+{
+ return m_WindowId;
+}
+
+void StatusNotifierItem::setWindowId(const QString &newWindowId)
+{
+ if (m_WindowId == newWindowId)
+ return;
+ m_WindowId = newWindowId;
+ emit WindowIdChanged();
+}
+
+QString StatusNotifierItem::AttentionIconName() const
+{
+ return m_AttentionIconName;
+}
+
+void StatusNotifierItem::setAttentionIconName(const QString &newAttentionIconName)
+{
+ if (m_AttentionIconName == newAttentionIconName)
+ return;
+ m_AttentionIconName = newAttentionIconName;
+ emit AttentionIconNameChanged();
+}
+
+QString StatusNotifierItem::AttentionMovieName() const
+{
+ return m_AttentionMovieName;
+}
+
+void StatusNotifierItem::setAttentionMovieName(const QString &newAttentionMovieName)
+{
+ if (m_AttentionMovieName == newAttentionMovieName)
+ return;
+ m_AttentionMovieName = newAttentionMovieName;
+ emit AttentionMovieNameChanged();
+}
diff --git a/src/lib/xdgstatusnotifieritem.h b/src/lib/xdgstatusnotifieritem.h
new file mode 100644
index 0000000..7f881a4
--- /dev/null
+++ b/src/lib/xdgstatusnotifieritem.h
@@ -0,0 +1,119 @@
+#ifndef XDGSTATUSNOTIFIERITEM_H
+#define XDGSTATUSNOTIFIERITEM_H
+
+#include <QObject>
+
+#define XDG_CATEGORY_APPLICATIONSTATUS "ApplicationStatus"
+#define XDG_CATEGORY_COMMUNICATIONS "Communications"
+#define XDG_CATEGORY_SYSTEMSERVICE "SystemServices"
+#define XDG_CATEGORY_HARDWARE "Hardware"
+
+#define XDG_STATUS_PASSIVE "Passive"
+#define XDG_STATUS_ACTIVE "Active"
+#define XDG_STATUS_NEEDSATTENTION "NeedsAttention"
+
+namespace xdg {
+
+ class StatusNotifierItem : public QObject
+ {
+ Q_OBJECT
+
+ /* This properties are used for DBus object connection */
+ Q_PROPERTY(QString Category READ Category WRITE setCategory NOTIFY CategoryChanged)
+ Q_PROPERTY(QString Id READ Id WRITE setId NOTIFY IdChanged)
+ Q_PROPERTY(QString Title READ Title WRITE setTitle NOTIFY TitleChanged)
+ Q_PROPERTY(QString Status READ Status WRITE setStatus NOTIFY StatusChanged)
+ Q_PROPERTY(QString WindowId READ WindowId WRITE setWindowId NOTIFY WindowIdChanged)
+ Q_PROPERTY(QString IconName READ IconName WRITE setIconName NOTIFY IconNameChanged)
+ Q_PROPERTY(QString OverlayIconName READ OverlayIconName WRITE setOverlayIconName NOTIFY overlayIconNameChanged)
+ Q_PROPERTY(QString AttentionIconName READ AttentionIconName WRITE setAttentionIconName NOTIFY AttentionIconNameChanged)
+ Q_PROPERTY(QString AttentionMovieName READ AttentionMovieName WRITE setAttentionMovieName NOTIFY AttentionMovieNameChanged)
+
+ /* This properties are used for QML */
+ Q_PROPERTY(QString category READ Category WRITE setCategory NOTIFY CategoryChanged)
+ Q_PROPERTY(QString idItem READ Id WRITE setId NOTIFY IdChanged)
+ Q_PROPERTY(QString title READ Title WRITE setTitle NOTIFY TitleChanged)
+ Q_PROPERTY(QString status READ Status WRITE setStatus NOTIFY StatusChanged)
+ Q_PROPERTY(QString windowId READ WindowId WRITE setWindowId NOTIFY WindowIdChanged)
+ Q_PROPERTY(QString iconName READ IconName WRITE setIconName NOTIFY IconNameChanged)
+ Q_PROPERTY(QString overlayIconName READ OverlayIconName WRITE setOverlayIconName NOTIFY overlayIconNameChanged)
+ Q_PROPERTY(QString attentionIconName READ AttentionIconName WRITE setAttentionIconName NOTIFY AttentionIconNameChanged)
+ Q_PROPERTY(QString attentionMovieName READ AttentionMovieName WRITE setAttentionMovieName NOTIFY AttentionMovieNameChanged)
+
+ public:
+ const QStringList xdgCategories {XDG_CATEGORY_APPLICATIONSTATUS, XDG_CATEGORY_COMMUNICATIONS, XDG_CATEGORY_SYSTEMSERVICE, XDG_CATEGORY_HARDWARE};
+ const QStringList xdgStatus {XDG_STATUS_PASSIVE, XDG_STATUS_ACTIVE, XDG_STATUS_NEEDSATTENTION};
+
+ explicit StatusNotifierItem(QObject *parent = nullptr);
+
+ QString Title() const;
+ void setTitle(const QString &newTitle);
+
+ QString IconName() const;
+ void setIconName(const QString &newIconName);
+
+ QString Status() const;
+ void setStatus(const QString &newStatus);
+
+
+ QString OverlayIconName() const;
+ void setOverlayIconName(const QString &newOverlayIconName);
+
+ QString Category() const;
+ void setCategory(const QString &newCategory);
+
+ QString Id() const;
+ void setId(const QString &newId);
+
+ QString WindowId() const;
+ void setWindowId(const QString &newWindowId);
+
+ QString AttentionIconName() const;
+ void setAttentionIconName(const QString &newAttentionIconName);
+
+ QString AttentionMovieName() const;
+ void setAttentionMovieName(const QString &newAttentionMovieName);
+
+ public slots:
+ /* DBus API */
+ virtual void Activate(uint x, uint y);
+ virtual void SecondaryActivate(uint x, uint y);
+
+ signals:
+ void NewTitle();
+ void NewIcon();
+ void NewAttentionIcon();
+ void NewOverlayIcon();
+ void NewToolTip();
+ void NewStatus(QString status);
+
+ void TitleChanged();
+ void IconNameChanged();
+ void StatusChanged();
+ void overlayIconNameChanged();
+ void CategoryChanged();
+ void IdChanged();
+ void WindowIdChanged();
+
+ void activated(uint x, uint y);
+ void secondaryActivated(uint x, uint y);
+
+
+ void AttentionIconNameChanged();
+
+ void AttentionMovieNameChanged();
+
+ private:
+ QString m_Title;
+ QString m_IconName;
+ QString m_Status {XDG_STATUS_ACTIVE};
+ QString m_OverlayIconName;
+ QString m_Category {XDG_CATEGORY_APPLICATIONSTATUS};
+ QString m_Id;
+ QString m_WindowId;
+ QString m_AttentionIconName;
+ QString m_AttentionMovieName;
+ };
+};
+
+#endif // XDGSTATUSNOTIFIERITEM_H
diff --git a/src/lib/xdgstatusnotifierwatcher.cpp b/src/lib/xdgstatusnotifierwatcher.cpp
new file mode 100644
index 0000000..c07a0f6
--- /dev/null
+++ b/src/lib/xdgstatusnotifierwatcher.cpp
@@ -0,0 +1,116 @@
+#include "xdgstatusnotifierwatcher.h"
+#include <statusnotifierwatcheradaptor.h>
+#include <XdgStatusNotifierItemIface.h>
+#include <QDBusConnection>
+#include <debug.h>
+
+using namespace xdg;
+
+StatusNotifierWatcher::StatusNotifierWatcher(QObject *parent)
+ : QObject{parent}
+{
+ QDBusConnection bus = QDBusConnection::sessionBus();
+ if (!bus.isConnected()) {
+ qCWarning(SYSTEM_TRAY) << "Cannot connect to the D-Bus session bus.\n" << "Please check your system settings and try again.\n";
+ }
+ if(!bus.registerService("org.kde.StatusNotifierWatcher"))
+ {
+ qCWarning(SYSTEM_TRAY) << "Don't register StatusNotifierWatcher service, probably already exists";
+ return;
+ }
+ qCDebug(SYSTEM_TRAY) << "Register StatusNotifierWatcher service";
+ new StatusNotifierWatcherAdaptor(this);
+ bus.registerObject("/StatusNotifierWatcher", this);
+
+ m_serviceWatcher = new QDBusServiceWatcher(this);
+ m_serviceWatcher->setConnection(bus);
+ m_serviceWatcher->setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
+
+ connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, this, &StatusNotifierWatcher::_serviceUnregistered);
+}
+
+StatusNotifierWatcher::~StatusNotifierWatcher()
+{
+
+}
+
+void StatusNotifierWatcher::RegisterStatusNotifierItem(const QString &service)
+{
+ QString path;
+ path = QStringLiteral("/StatusNotifierItem");
+ qCDebug(SYSTEM_TRAY) << "Watcher: RegisterStatusNotifierItem(" << service << ")";
+ QString notifierItemId = service + path;
+ if (m_items.contains(notifierItemId)) {
+ return;
+ }
+ m_serviceWatcher->addWatchedService(service);
+ if (QDBusConnection::sessionBus().interface()->isServiceRegistered(service).value()) {
+ // check if the service has registered a SystemTray object
+ org::kde::StatusNotifierItem trayclient(service, path, QDBusConnection::sessionBus());
+ if (trayclient.isValid()) {
+ qDebug() << "Registering" << notifierItemId << "to system tray";
+ m_items.append(notifierItemId);
+ emit StatusNotifierItemRegistered(notifierItemId);
+ } else {
+ m_serviceWatcher->removeWatchedService(service);
+ }
+ } else {
+ m_serviceWatcher->removeWatchedService(service);
+ }
+}
+
+void StatusNotifierWatcher::RegisterStatusNotifierHost(const QString &service)
+{
+ qCDebug(SYSTEM_TRAY) << "Watcher: RegisterStatusNotifierHost(" << service << ")";
+ if (m_hosts.contains(service)) {
+ return;
+ }
+ m_serviceWatcher->addWatchedService(service);
+ m_hosts.append(service);
+}
+
+QStringList StatusNotifierWatcher::RegisteredStatusNotifierItems() const
+{
+ return m_items;
+}
+
+
+bool StatusNotifierWatcher::IsStatusNotifierHostRegistered() const
+{
+ return m_hosts.size() > 0;
+}
+
+
+int StatusNotifierWatcher::ProtocolVersion() const
+{
+ return XDG_STATUS_NOTIFIER_WATCHER_PROTOCOL_VERSION;
+}
+
+void StatusNotifierWatcher::_serviceUnregistered(const QString &name)
+{
+ qCDebug(SYSTEM_TRAY) << "Service " << name << "unregistered (ITEMS:" << m_items << " HOSTS:" << m_hosts << ")";
+ m_serviceWatcher->removeWatchedService(name);
+
+ const QString match = name + QLatin1Char('/');
+ QStringList::Iterator it = m_items.begin();
+ while (it != m_items.end()) {
+ if (it->startsWith(match)) {
+ QString name = *it;
+ it = m_items.erase(it);
+ emit StatusNotifierItemUnregistered(name);
+ } else {
+ ++it;
+ }
+ }
+
+ it = m_hosts.begin();
+ while (it != m_hosts.end()) {
+ if (it->startsWith(match)) {
+ QString name = *it;
+ it = m_hosts.erase(it);
+ emit StatusNotifierHostUnregistered();
+ } else {
+ ++it;
+ }
+ }
+}
diff --git a/src/lib/xdgstatusnotifierwatcher.h b/src/lib/xdgstatusnotifierwatcher.h
new file mode 100644
index 0000000..8e2275c
--- /dev/null
+++ b/src/lib/xdgstatusnotifierwatcher.h
@@ -0,0 +1,48 @@
+#ifndef XDGSTATUSNOTIFIERWATCHER_H
+#define XDGSTATUSNOTIFIERWATCHER_H
+
+#include <QObject>
+
+class QDBusServiceWatcher;
+#define XDG_STATUS_NOTIFIER_WATCHER_PROTOCOL_VERSION 1
+
+namespace xdg {
+
+ class StatusNotifierWatcher: public QObject
+ {
+ Q_OBJECT
+
+ Q_PROPERTY(QStringList RegisteredStatusNotifierItems READ RegisteredStatusNotifierItems NOTIFY RegisteredStatusNotifierItemsChanged)
+ Q_PROPERTY(bool IsStatusNotifierHostRegistered READ IsStatusNotifierHostRegistered NOTIFY IsStatusNotifierHostRegisteredChanged)
+ Q_PROPERTY(int ProtocolVersion READ ProtocolVersion CONSTANT)
+
+ public:
+ StatusNotifierWatcher(QObject *parent = nullptr);
+ virtual ~StatusNotifierWatcher();
+
+ void RegisterStatusNotifierItem(const QString &service);
+ void RegisterStatusNotifierHost(const QString &service);
+
+ QStringList RegisteredStatusNotifierItems() const;
+ bool IsStatusNotifierHostRegistered() const;
+ int ProtocolVersion() const;
+
+ signals:
+ void StatusNotifierItemRegistered(QString id);
+ void StatusNotifierItemUnregistered(QString id);
+ void StatusNotifierHostRegistered();
+ void StatusNotifierHostUnregistered();
+ void RegisteredStatusNotifierItemsChanged();
+ void IsStatusNotifierHostRegisteredChanged();
+
+ private:
+ QStringList m_hosts;
+ QStringList m_items;
+ QDBusServiceWatcher *m_serviceWatcher = nullptr;
+
+ private slots:
+ void _serviceUnregistered(const QString &name);
+ };
+};
+
+#endif // XDGSTATUSNOTIFIERWATCHER_H