From 38ec91fe08f57a758b191e90be7c455c1250adae Mon Sep 17 00:00:00 2001 From: rekols Date: Sat, 5 Jun 2021 14:07:46 +0800 Subject: [PATCH] Integrated dbusmenuqt --- CMakeLists.txt | 17 +- src/libdbusmenuqt/com.canonical.dbusmenu.xml | 49 ++ src/libdbusmenuqt/dbusmenuimporter.cpp | 549 +++++++++++++++++++ src/libdbusmenuqt/dbusmenuimporter.h | 111 ++++ src/libdbusmenuqt/dbusmenushortcut_p.cpp | 84 +++ src/libdbusmenuqt/dbusmenushortcut_p.h | 39 ++ src/libdbusmenuqt/dbusmenutypes_p.cpp | 136 +++++ src/libdbusmenuqt/dbusmenutypes_p.h | 96 ++++ src/libdbusmenuqt/utils.cpp | 65 +++ src/libdbusmenuqt/utils.h | 31 ++ src/systemtray/statusnotifieritemsource.cpp | 39 +- src/systemtray/statusnotifieritemsource.h | 22 + src/systemtray/statusnotifierwatcher.cpp | 22 + src/systemtray/statusnotifierwatcher.h | 22 + src/systemtray/systemtraymodel.cpp | 20 + src/systemtray/systemtraymodel.h | 20 + 16 files changed, 1314 insertions(+), 8 deletions(-) create mode 100644 src/libdbusmenuqt/com.canonical.dbusmenu.xml create mode 100644 src/libdbusmenuqt/dbusmenuimporter.cpp create mode 100644 src/libdbusmenuqt/dbusmenuimporter.h create mode 100644 src/libdbusmenuqt/dbusmenushortcut_p.cpp create mode 100644 src/libdbusmenuqt/dbusmenushortcut_p.h create mode 100644 src/libdbusmenuqt/dbusmenutypes_p.cpp create mode 100644 src/libdbusmenuqt/dbusmenutypes_p.h create mode 100644 src/libdbusmenuqt/utils.cpp create mode 100644 src/libdbusmenuqt/utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ff3e6e..42b3ca3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5 CONFIG REQUIRED Widgets DBus X11Extras Concurrent Svg LinguistTools QuickControls2) find_package(KF5WindowSystem REQUIRED) -find_package(dbusmenu-qt5 REQUIRED) find_package(FishUI REQUIRED) set(SRCS @@ -34,6 +33,11 @@ set(SRCS src/systemtray/systemtraymodel.cpp src/systemtray/statusnotifierwatcher.cpp + src/libdbusmenuqt/dbusmenuimporter.cpp + src/libdbusmenuqt/dbusmenushortcut_p.cpp + src/libdbusmenuqt/dbusmenutypes_p.cpp + src/libdbusmenuqt/utils.cpp + qml.qrc ) @@ -52,7 +56,15 @@ qt5_add_dbus_interface(SRCS ${statusnotifieritem_xml} statusnotifieritem_interfa qt5_add_dbus_adaptor(SRCS src/systemtray/org.kde.StatusNotifierWatcher.xml src/systemtray/statusnotifierwatcher.h StatusNotifierWatcher) -add_executable(cutefish-statusbar ${SRCS}) +# libdbusmenuqt +set_source_files_properties(src/libdbusmenuqt/com.canonical.dbusmenu.xml PROPERTIES + NO_NAMESPACE true + INCLUDE "src/libdbusmenuqt/dbusmenutypes_p.h" + CLASSNAME DBusMenuInterface +) +qt_add_dbus_interface(libdbusmenu_SRCS src/libdbusmenuqt/com.canonical.dbusmenu.xml dbusmenu_interface) + +add_executable(cutefish-statusbar ${SRCS} ${libdbusmenu_SRCS}) target_link_libraries(cutefish-statusbar PRIVATE @@ -68,7 +80,6 @@ target_link_libraries(cutefish-statusbar FishUI KF5::WindowSystem - dbusmenu-qt5 ) file(GLOB TS_FILES translations/*.ts) diff --git a/src/libdbusmenuqt/com.canonical.dbusmenu.xml b/src/libdbusmenuqt/com.canonical.dbusmenu.xml new file mode 100644 index 0000000..c90776a --- /dev/null +++ b/src/libdbusmenuqt/com.canonical.dbusmenu.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libdbusmenuqt/dbusmenuimporter.cpp b/src/libdbusmenuqt/dbusmenuimporter.cpp new file mode 100644 index 0000000..fde7abc --- /dev/null +++ b/src/libdbusmenuqt/dbusmenuimporter.cpp @@ -0,0 +1,549 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "dbusmenuimporter.h" + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Local +#include "dbusmenushortcut_p.h" +#include "dbusmenutypes_p.h" +#include "utils.h" + +// Generated +#include "dbusmenu_interface.h" + +//#define BENCHMARK +#ifdef BENCHMARK +static QTime sChrono; +#endif + +#define DMRETURN_IF_FAIL(cond) \ + if (!(cond)) { \ + qCWarning() << "Condition failed: " #cond; \ + return; \ + } + +static const char *DBUSMENU_PROPERTY_ID = "_dbusmenu_id"; +static const char *DBUSMENU_PROPERTY_ICON_NAME = "_dbusmenu_icon_name"; +static const char *DBUSMENU_PROPERTY_ICON_DATA_HASH = "_dbusmenu_icon_data_hash"; + +static QAction *createKdeTitle(QAction *action, QWidget *parent) +{ + QToolButton *titleWidget = new QToolButton(nullptr); + QFont font = titleWidget->font(); + font.setBold(true); + titleWidget->setFont(font); + titleWidget->setIcon(action->icon()); + titleWidget->setText(action->text()); + titleWidget->setDown(true); + titleWidget->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + + QWidgetAction *titleAction = new QWidgetAction(parent); + titleAction->setDefaultWidget(titleWidget); + return titleAction; +} + +class DBusMenuImporterPrivate +{ +public: + DBusMenuImporter *q; + + DBusMenuInterface *m_interface; + QMenu *m_menu; + using ActionForId = QMap; + ActionForId m_actionForId; + QTimer *m_pendingLayoutUpdateTimer; + + QSet m_idsRefreshedByAboutToShow; + QSet m_pendingLayoutUpdates; + + QDBusPendingCallWatcher *refresh(int id) + { + auto call = m_interface->GetLayout(id, 1, QStringList()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, q); + watcher->setProperty(DBUSMENU_PROPERTY_ID, id); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, &DBusMenuImporter::slotGetLayoutFinished); + + return watcher; + } + + QMenu *createMenu(QWidget *parent) + { + QMenu *menu = q->createMenu(parent); + return menu; + } + + /** + * Init all the immutable action properties here + * TODO: Document immutable properties? + * + * Note: we remove properties we handle from the map (using QMap::take() + * instead of QMap::value()) to avoid warnings about these properties in + * updateAction() + */ + QAction *createAction(int id, const QVariantMap &_map, QWidget *parent) + { + QVariantMap map = _map; + QAction *action = new QAction(parent); + action->setProperty(DBUSMENU_PROPERTY_ID, id); + + QString type = map.take(QStringLiteral("type")).toString(); + if (type == QLatin1String("separator")) { + action->setSeparator(true); + } + + if (map.take(QStringLiteral("children-display")).toString() == QLatin1String("submenu")) { + QMenu *menu = createMenu(parent); + action->setMenu(menu); + } + + QString toggleType = map.take(QStringLiteral("toggle-type")).toString(); + if (!toggleType.isEmpty()) { + action->setCheckable(true); + if (toggleType == QLatin1String("radio")) { + QActionGroup *group = new QActionGroup(action); + group->addAction(action); + } + } + + bool isKdeTitle = map.take(QStringLiteral("x-kde-title")).toBool(); + updateAction(action, map, map.keys()); + + if (isKdeTitle) { + action = createKdeTitle(action, parent); + } + + return action; + } + + /** + * Update mutable properties of an action. A property may be listed in + * requestedProperties but not in map, this means we should use the default value + * for this property. + * + * @param action the action to update + * @param map holds the property values + * @param requestedProperties which properties has been requested + */ + void updateAction(QAction *action, const QVariantMap &map, const QStringList &requestedProperties) + { + Q_FOREACH (const QString &key, requestedProperties) { + updateActionProperty(action, key, map.value(key)); + } + } + + void updateActionProperty(QAction *action, const QString &key, const QVariant &value) + { + if (key == QLatin1String("label")) { + updateActionLabel(action, value); + } else if (key == QLatin1String("enabled")) { + updateActionEnabled(action, value); + } else if (key == QLatin1String("toggle-state")) { + updateActionChecked(action, value); + } else if (key == QLatin1String("icon-name")) { + updateActionIconByName(action, value); + } else if (key == QLatin1String("icon-data")) { + updateActionIconByData(action, value); + } else if (key == QLatin1String("visible")) { + updateActionVisible(action, value); + } else if (key == QLatin1String("shortcut")) { + updateActionShortcut(action, value); + } else { + qDebug() << "Unhandled property update" << key; + } + } + + void updateActionLabel(QAction *action, const QVariant &value) + { + QString text = swapMnemonicChar(value.toString(), '_', '&'); + action->setText(text); + } + + void updateActionEnabled(QAction *action, const QVariant &value) + { + action->setEnabled(value.isValid() ? value.toBool() : true); + } + + void updateActionChecked(QAction *action, const QVariant &value) + { + if (action->isCheckable() && value.isValid()) { + action->setChecked(value.toInt() == 1); + } + } + + void updateActionIconByName(QAction *action, const QVariant &value) + { + const QString iconName = value.toString(); + const QString previous = action->property(DBUSMENU_PROPERTY_ICON_NAME).toString(); + if (previous == iconName) { + return; + } + action->setProperty(DBUSMENU_PROPERTY_ICON_NAME, iconName); + if (iconName.isEmpty()) { + action->setIcon(QIcon()); + return; + } + action->setIcon(q->iconForName(iconName)); + } + + void updateActionIconByData(QAction *action, const QVariant &value) + { + const QByteArray data = value.toByteArray(); + uint dataHash = qHash(data); + uint previousDataHash = action->property(DBUSMENU_PROPERTY_ICON_DATA_HASH).toUInt(); + if (previousDataHash == dataHash) { + return; + } + action->setProperty(DBUSMENU_PROPERTY_ICON_DATA_HASH, dataHash); + QPixmap pix; + if (!pix.loadFromData(data)) { + qDebug() << "Failed to decode icon-data property for action" << action->text(); + action->setIcon(QIcon()); + return; + } + action->setIcon(QIcon(pix)); + } + + void updateActionVisible(QAction *action, const QVariant &value) + { + action->setVisible(value.isValid() ? value.toBool() : true); + } + + void updateActionShortcut(QAction *action, const QVariant &value) + { + QDBusArgument arg = value.value(); + DBusMenuShortcut dmShortcut; + arg >> dmShortcut; + QKeySequence keySequence = dmShortcut.toKeySequence(); + action->setShortcut(keySequence); + } + + QMenu *menuForId(int id) const + { + if (id == 0) { + return q->menu(); + } + QAction *action = m_actionForId.value(id); + if (!action) { + return nullptr; + } + return action->menu(); + } + + void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList); + + void sendEvent(int id, const QString &eventId) + { + m_interface->Event(id, eventId, QDBusVariant(QString()), 0u); + } +}; + +DBusMenuImporter::DBusMenuImporter(const QString &service, const QString &path, QObject *parent) + : QObject(parent) + , d(new DBusMenuImporterPrivate) +{ + DBusMenuTypes_register(); + + d->q = this; + d->m_interface = new DBusMenuInterface(service, path, QDBusConnection::sessionBus(), this); + d->m_menu = nullptr; + + d->m_pendingLayoutUpdateTimer = new QTimer(this); + d->m_pendingLayoutUpdateTimer->setSingleShot(true); + connect(d->m_pendingLayoutUpdateTimer, &QTimer::timeout, this, &DBusMenuImporter::processPendingLayoutUpdates); + + connect(d->m_interface, &DBusMenuInterface::LayoutUpdated, this, &DBusMenuImporter::slotLayoutUpdated); + connect(d->m_interface, &DBusMenuInterface::ItemActivationRequested, this, &DBusMenuImporter::slotItemActivationRequested); + connect(d->m_interface, + &DBusMenuInterface::ItemsPropertiesUpdated, + this, + [this](const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) { + d->slotItemsPropertiesUpdated(updatedList, removedList); + }); + + d->refresh(0); +} + +DBusMenuImporter::~DBusMenuImporter() +{ + // Do not use "delete d->m_menu": even if we are being deleted we should + // leave enough time for the menu to finish what it was doing, for example + // if it was being displayed. + d->m_menu->deleteLater(); + delete d; +} + +void DBusMenuImporter::slotLayoutUpdated(uint revision, int parentId) +{ + Q_UNUSED(revision) + if (d->m_idsRefreshedByAboutToShow.remove(parentId)) { + return; + } + d->m_pendingLayoutUpdates << parentId; + if (!d->m_pendingLayoutUpdateTimer->isActive()) { + d->m_pendingLayoutUpdateTimer->start(); + } +} + +void DBusMenuImporter::processPendingLayoutUpdates() +{ + QSet ids = d->m_pendingLayoutUpdates; + d->m_pendingLayoutUpdates.clear(); + Q_FOREACH (int id, ids) { + d->refresh(id); + } +} + +QMenu *DBusMenuImporter::menu() const +{ + if (!d->m_menu) { + d->m_menu = d->createMenu(nullptr); + } + return d->m_menu; +} + +void DBusMenuImporterPrivate::slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList) +{ + Q_FOREACH (const DBusMenuItem &item, updatedList) { + QAction *action = m_actionForId.value(item.id); + if (!action) { + // We don't know this action. It probably is in a menu we haven't fetched yet. + continue; + } + + QVariantMap::ConstIterator it = item.properties.constBegin(), end = item.properties.constEnd(); + for (; it != end; ++it) { + updateActionProperty(action, it.key(), it.value()); + } + } + + Q_FOREACH (const DBusMenuItemKeys &item, removedList) { + QAction *action = m_actionForId.value(item.id); + if (!action) { + // We don't know this action. It probably is in a menu we haven't fetched yet. + continue; + } + + Q_FOREACH (const QString &key, item.properties) { + updateActionProperty(action, key, QVariant()); + } + } +} + +QAction *DBusMenuImporter::actionForId(int id) const +{ + return d->m_actionForId.value(id); +} + +void DBusMenuImporter::slotItemActivationRequested(int id, uint /*timestamp*/) +{ + QAction *action = d->m_actionForId.value(id); + // DMRETURN_IF_FAIL(action); + actionActivationRequested(action); +} + +void DBusMenuImporter::slotGetLayoutFinished(QDBusPendingCallWatcher *watcher) +{ + int parentId = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); + watcher->deleteLater(); + + QMenu *menu = d->menuForId(parentId); + + QDBusPendingReply reply = *watcher; + if (!reply.isValid()) { + qDebug() << reply.error().message(); + if (menu) { + emit menuUpdated(menu); + } + return; + } + +#ifdef BENCHMARK + DMDEBUG << "- items received:" << sChrono.elapsed() << "ms"; +#endif + DBusMenuLayoutItem rootItem = reply.argumentAt<1>(); + + if (!menu) { + qDebug() << "No menu for id" << parentId; + return; + } + + // remove outdated actions + QSet newDBusMenuItemIds; + newDBusMenuItemIds.reserve(rootItem.children.count()); + for (const DBusMenuLayoutItem &item : qAsConst(rootItem.children)) { + newDBusMenuItemIds << item.id; + } + for (QAction *action : menu->actions()) { + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + if (!newDBusMenuItemIds.contains(id)) { + // Not calling removeAction() as QMenu will immediately close when it becomes empty, + // which can happen when an application completely reloads this menu. + // When the action is deleted deferred, it is removed from the menu. + action->deleteLater(); + if (action->menu()) { + action->menu()->deleteLater(); + } + d->m_actionForId.remove(id); + } + } + + // insert or update new actions into our menu + for (const DBusMenuLayoutItem &dbusMenuItem : qAsConst(rootItem.children)) { + DBusMenuImporterPrivate::ActionForId::Iterator it = d->m_actionForId.find(dbusMenuItem.id); + QAction *action = nullptr; + if (it == d->m_actionForId.end()) { + int id = dbusMenuItem.id; + action = d->createAction(id, dbusMenuItem.properties, menu); + d->m_actionForId.insert(id, action); + + connect(action, &QObject::destroyed, this, [this, id]() { + d->m_actionForId.remove(id); + }); + + connect(action, &QAction::triggered, this, [id, this]() { + sendClickedEvent(id); + }); + + if (QMenu *menuAction = action->menu()) { + connect(menuAction, &QMenu::aboutToShow, this, &DBusMenuImporter::slotMenuAboutToShow, Qt::UniqueConnection); + } + connect(menu, &QMenu::aboutToHide, this, &DBusMenuImporter::slotMenuAboutToHide, Qt::UniqueConnection); + + menu->addAction(action); + } else { + action = *it; + QStringList filteredKeys = dbusMenuItem.properties.keys(); + filteredKeys.removeOne("type"); + filteredKeys.removeOne("toggle-type"); + filteredKeys.removeOne("children-display"); + d->updateAction(*it, dbusMenuItem.properties, filteredKeys); + // Move the action to the tail so we can keep the order same as the dbus request. + menu->removeAction(action); + menu->addAction(action); + } + } + + emit menuUpdated(menu); +} + +void DBusMenuImporter::sendClickedEvent(int id) +{ + d->sendEvent(id, QStringLiteral("clicked")); +} + +void DBusMenuImporter::updateMenu() +{ + updateMenu(DBusMenuImporter::menu()); +} + +void DBusMenuImporter::updateMenu(QMenu *menu) +{ + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + + auto call = d->m_interface->AboutToShow(id); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + watcher->setProperty(DBUSMENU_PROPERTY_ID, id); + connect(watcher, &QDBusPendingCallWatcher::finished, this, &DBusMenuImporter::slotAboutToShowDBusCallFinished); + + // Firefox deliberately ignores "aboutToShow" whereas Qt ignores" opened", so we'll just send both all the time... + d->sendEvent(id, QStringLiteral("opened")); +} + +void DBusMenuImporter::slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *watcher) +{ + int id = watcher->property(DBUSMENU_PROPERTY_ID).toInt(); + watcher->deleteLater(); + + QMenu *menu = d->menuForId(id); + if (!menu) { + return; + } + + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + qDebug() << "Call to AboutToShow() failed:" << reply.error().message(); + Q_EMIT menuUpdated(menu); + return; + } + // Note, this isn't used by Qt's QPT - but we get a LayoutChanged emitted before + // this returns, which equates to the same thing + bool needRefresh = reply.argumentAt<0>(); + + if (needRefresh || menu->actions().isEmpty()) { + d->m_idsRefreshedByAboutToShow << id; + d->refresh(id); + } else if (menu) { + Q_EMIT menuUpdated(menu); + } +} + +void DBusMenuImporter::slotMenuAboutToHide() +{ + QMenu *menu = qobject_cast(sender()); + Q_ASSERT(menu); + + QAction *action = menu->menuAction(); + Q_ASSERT(action); + + int id = action->property(DBUSMENU_PROPERTY_ID).toInt(); + d->sendEvent(id, QStringLiteral("closed")); +} + +void DBusMenuImporter::slotMenuAboutToShow() +{ + QMenu *menu = qobject_cast(sender()); + Q_ASSERT(menu); + + updateMenu(menu); +} + +QMenu *DBusMenuImporter::createMenu(QWidget *parent) +{ + return new QMenu(parent); +} + +QIcon DBusMenuImporter::iconForName(const QString & /*name*/) +{ + return QIcon(); +} + +#include "moc_dbusmenuimporter.cpp" diff --git a/src/libdbusmenuqt/dbusmenuimporter.h b/src/libdbusmenuqt/dbusmenuimporter.h new file mode 100644 index 0000000..ff5c18f --- /dev/null +++ b/src/libdbusmenuqt/dbusmenuimporter.h @@ -0,0 +1,111 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUIMPORTER_H +#define DBUSMENUIMPORTER_H + +// Qt +#include + +class QAction; +class QDBusPendingCallWatcher; +class QIcon; +class QMenu; + +class DBusMenuImporterPrivate; + +/** + * A DBusMenuImporter instance can recreate a menu serialized over DBus by + * DBusMenuExporter + */ +class DBusMenuImporter : public QObject +{ + Q_OBJECT +public: + /** + * Creates a DBusMenuImporter listening over DBus on service, path + */ + DBusMenuImporter(const QString &service, const QString &path, QObject *parent = nullptr); + + ~DBusMenuImporter() override; + + QAction *actionForId(int id) const; + + /** + * The menu created from listening to the DBusMenuExporter over DBus + */ + QMenu *menu() const; + +public Q_SLOTS: + /** + * Load the menu + * + * Will emit menuUpdated() when complete. + * This should be done before showing a menu + */ + void updateMenu(); + + void updateMenu(QMenu *menu); + +Q_SIGNALS: + /** + * Emitted after a call to updateMenu(). + * @see updateMenu() + */ + void menuUpdated(QMenu *); + + /** + * Emitted when the exporter was asked to activate an action + */ + void actionActivationRequested(QAction *); + +protected: + /** + * Must create a menu, may be customized to fit host appearance. + * Default implementation creates a simple QMenu. + */ + virtual QMenu *createMenu(QWidget *parent); + + /** + * Must convert a name into an icon. + * Default implementation returns a null icon. + */ + virtual QIcon iconForName(const QString &); + +private Q_SLOTS: + void sendClickedEvent(int); + void slotMenuAboutToShow(); + void slotMenuAboutToHide(); + void slotAboutToShowDBusCallFinished(QDBusPendingCallWatcher *); + void slotItemActivationRequested(int id, uint timestamp); + void processPendingLayoutUpdates(); + void slotLayoutUpdated(uint revision, int parentId); + void slotGetLayoutFinished(QDBusPendingCallWatcher *); + +private: + Q_DISABLE_COPY(DBusMenuImporter) + DBusMenuImporterPrivate *const d; + friend class DBusMenuImporterPrivate; + + // Use Q_PRIVATE_SLOT to avoid exposing DBusMenuItemList + Q_PRIVATE_SLOT(d, void slotItemsPropertiesUpdated(const DBusMenuItemList &updatedList, const DBusMenuItemKeysList &removedList)) +}; + +#endif /* DBUSMENUIMPORTER_H */ diff --git a/src/libdbusmenuqt/dbusmenushortcut_p.cpp b/src/libdbusmenuqt/dbusmenushortcut_p.cpp new file mode 100644 index 0000000..e67fc0f --- /dev/null +++ b/src/libdbusmenuqt/dbusmenushortcut_p.cpp @@ -0,0 +1,84 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "dbusmenushortcut_p.h" + +// Qt +#include + +static const int QT_COLUMN = 0; +static const int DM_COLUMN = 1; + +static void processKeyTokens(QStringList *tokens, int srcCol, int dstCol) +{ + struct Row { + const char *zero; + const char *one; + const char *operator[](int col) const + { + return col == 0 ? zero : one; + } + }; + static const Row table[] = {{"Meta", "Super"}, + {"Ctrl", "Control"}, + // Special cases for compatibility with libdbusmenu-glib which uses + // "plus" for "+" and "minus" for "-". + // cf https://bugs.launchpad.net/libdbusmenu-qt/+bug/712565 + {"+", "plus"}, + {"-", "minus"}, + {nullptr, nullptr}}; + + const Row *ptr = table; + for (; ptr->zero != nullptr; ++ptr) { + const char *from = (*ptr)[srcCol]; + const char *to = (*ptr)[dstCol]; + tokens->replaceInStrings(from, to); + } +} + +DBusMenuShortcut DBusMenuShortcut::fromKeySequence(const QKeySequence &sequence) +{ + QString string = sequence.toString(); + DBusMenuShortcut shortcut; + QStringList tokens = string.split(QStringLiteral(", ")); + Q_FOREACH (QString token, tokens) { + // Hack: Qt::CTRL | Qt::Key_Plus is turned into the string "Ctrl++", + // but we don't want the call to token.split() to consider the + // second '+' as a separator so we replace it with its final value. + token.replace(QLatin1String("++"), QLatin1String("+plus")); + QStringList keyTokens = token.split('+'); + processKeyTokens(&keyTokens, QT_COLUMN, DM_COLUMN); + shortcut << keyTokens; + } + return shortcut; +} + +QKeySequence DBusMenuShortcut::toKeySequence() const +{ + QStringList tmp; + Q_FOREACH (const QStringList &keyTokens_, *this) { + QStringList keyTokens = keyTokens_; + processKeyTokens(&keyTokens, DM_COLUMN, QT_COLUMN); + tmp << keyTokens.join(QLatin1String("+")); + } + QString string = tmp.join(QLatin1String(", ")); + return QKeySequence::fromString(string); +} diff --git a/src/libdbusmenuqt/dbusmenushortcut_p.h b/src/libdbusmenuqt/dbusmenushortcut_p.h new file mode 100644 index 0000000..ca06e7a --- /dev/null +++ b/src/libdbusmenuqt/dbusmenushortcut_p.h @@ -0,0 +1,39 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUSHORTCUT_H +#define DBUSMENUSHORTCUT_H + +// Qt +#include +#include + +class QKeySequence; + +class DBusMenuShortcut : public QList +{ +public: + QKeySequence toKeySequence() const; + static DBusMenuShortcut fromKeySequence(const QKeySequence &); +}; + +Q_DECLARE_METATYPE(DBusMenuShortcut) + +#endif /* DBUSMENUSHORTCUT_H */ diff --git a/src/libdbusmenuqt/dbusmenutypes_p.cpp b/src/libdbusmenuqt/dbusmenutypes_p.cpp new file mode 100644 index 0000000..eb16a82 --- /dev/null +++ b/src/libdbusmenuqt/dbusmenutypes_p.cpp @@ -0,0 +1,136 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#include "dbusmenutypes_p.h" + +// Local +#include "dbusmenushortcut_p.h" + +// Qt +#include +#include + +//// DBusMenuItem +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &obj) +{ + argument.beginStructure(); + argument << obj.id << obj.properties; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &obj) +{ + argument.beginStructure(); + argument >> obj.id >> obj.properties; + argument.endStructure(); + return argument; +} + +//// DBusMenuItemKeys +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &obj) +{ + argument.beginStructure(); + argument << obj.id << obj.properties; + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &obj) +{ + argument.beginStructure(); + argument >> obj.id >> obj.properties; + argument.endStructure(); + return argument; +} + +//// DBusMenuLayoutItem +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &obj) +{ + argument.beginStructure(); + argument << obj.id << obj.properties; + argument.beginArray(qMetaTypeId()); + Q_FOREACH (const DBusMenuLayoutItem &child, obj.children) { + argument << QDBusVariant(QVariant::fromValue(child)); + } + argument.endArray(); + argument.endStructure(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &obj) +{ + argument.beginStructure(); + argument >> obj.id >> obj.properties; + argument.beginArray(); + while (!argument.atEnd()) { + QDBusVariant dbusVariant; + argument >> dbusVariant; + QDBusArgument childArgument = dbusVariant.variant().value(); + + DBusMenuLayoutItem child; + childArgument >> child; + obj.children.append(child); + } + argument.endArray(); + argument.endStructure(); + return argument; +} + +//// DBusMenuShortcut +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &obj) +{ + argument.beginArray(qMetaTypeId()); + typename QList::ConstIterator it = obj.constBegin(); + typename QList::ConstIterator end = obj.constEnd(); + for (; it != end; ++it) + argument << *it; + argument.endArray(); + return argument; +} + +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &obj) +{ + argument.beginArray(); + obj.clear(); + while (!argument.atEnd()) { + QStringList item; + argument >> item; + obj.push_back(item); + } + argument.endArray(); + return argument; +} + +void DBusMenuTypes_register() +{ + static bool registered = false; + if (registered) { + return; + } + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + registered = true; +} diff --git a/src/libdbusmenuqt/dbusmenutypes_p.h b/src/libdbusmenuqt/dbusmenutypes_p.h new file mode 100644 index 0000000..28739cf --- /dev/null +++ b/src/libdbusmenuqt/dbusmenutypes_p.h @@ -0,0 +1,96 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2009 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef DBUSMENUTYPES_P_H +#define DBUSMENUTYPES_P_H + +// Qt +#include +#include +#include + +class QDBusArgument; + +//// DBusMenuItem +/** + * Internal struct used to communicate on DBus + */ +struct DBusMenuItem { + int id; + QVariantMap properties; +}; + +Q_DECLARE_METATYPE(DBusMenuItem) + +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItem &item); +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItem &item); + +typedef QList DBusMenuItemList; + +Q_DECLARE_METATYPE(DBusMenuItemList) + +//// DBusMenuItemKeys +/** + * Represents a list of keys for a menu item + */ +struct DBusMenuItemKeys { + int id; + QStringList properties; +}; + +Q_DECLARE_METATYPE(DBusMenuItemKeys) + +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuItemKeys &); +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuItemKeys &); + +typedef QList DBusMenuItemKeysList; + +Q_DECLARE_METATYPE(DBusMenuItemKeysList) + +//// DBusMenuLayoutItem +/** + * Represents an item with its children. GetLayout() returns a + * DBusMenuLayoutItemList. + */ +struct DBusMenuLayoutItem; +struct DBusMenuLayoutItem { + int id; + QVariantMap properties; + QList children; +}; + +Q_DECLARE_METATYPE(DBusMenuLayoutItem) + +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuLayoutItem &); +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuLayoutItem &); + +typedef QList DBusMenuLayoutItemList; + +Q_DECLARE_METATYPE(DBusMenuLayoutItemList) + +//// DBusMenuShortcut + +class DBusMenuShortcut; + +QDBusArgument &operator<<(QDBusArgument &argument, const DBusMenuShortcut &); +const QDBusArgument &operator>>(const QDBusArgument &argument, DBusMenuShortcut &); + +void DBusMenuTypes_register(); +#endif /* DBUSMENUTYPES_P_H */ diff --git a/src/libdbusmenuqt/utils.cpp b/src/libdbusmenuqt/utils.cpp new file mode 100644 index 0000000..ec83c04 --- /dev/null +++ b/src/libdbusmenuqt/utils.cpp @@ -0,0 +1,65 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "utils.h" + +// Qt +#include + +QString swapMnemonicChar(const QString &in, const char src, const char dst) +{ + QString out; + bool mnemonicFound = false; + + for (int pos = 0; pos < in.length();) { + QChar ch = in[pos]; + if (ch == src) { + if (pos == in.length() - 1) { + // 'src' at the end of string, skip it + ++pos; + } else { + if (in[pos + 1] == src) { + // A real 'src' + out += src; + pos += 2; + } else if (!mnemonicFound) { + // We found the mnemonic + mnemonicFound = true; + out += dst; + ++pos; + } else { + // We already have a mnemonic, just skip the char + ++pos; + } + } + } else if (ch == dst) { + // Escape 'dst' + out += dst; + out += dst; + ++pos; + } else { + out += ch; + ++pos; + } + } + + return out; +} diff --git a/src/libdbusmenuqt/utils.h b/src/libdbusmenuqt/utils.h new file mode 100644 index 0000000..329443b --- /dev/null +++ b/src/libdbusmenuqt/utils.h @@ -0,0 +1,31 @@ +/* This file is part of the dbusmenu-qt library + Copyright 2010 Canonical + Author: Aurelien Gateau + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License (LGPL) as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later + version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +#ifndef UTILS_H +#define UTILS_H + +class QString; + +/** + * Swap mnemonic char: Qt uses '&', while dbusmenu uses '_' + */ +QString swapMnemonicChar(const QString &in, const char src, const char dst); + +#endif /* UTILS_P_H */ diff --git a/src/systemtray/statusnotifieritemsource.cpp b/src/systemtray/statusnotifieritemsource.cpp index a6df55e..652e774 100644 --- a/src/systemtray/statusnotifieritemsource.cpp +++ b/src/systemtray/statusnotifieritemsource.cpp @@ -1,8 +1,31 @@ +/*************************************************************************** + * * + * Copyright (C) 2021 Reion Wong * + * Copyright (C) 2009 Marco Martin * + * Copyright (C) 2009 Matthieu Gallien * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + #include "statusnotifieritemsource.h" #include "systemtraytypes.h" +#include "../libdbusmenuqt/dbusmenuimporter.h" + #include -#include #include class MenuImporter : public DBusMenuImporter @@ -67,7 +90,8 @@ StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId StatusNotifierItemSource::~StatusNotifierItemSource() { - delete m_statusNotifierItemInterface; + if (m_statusNotifierItemInterface) + delete m_statusNotifierItemInterface; } QString StatusNotifierItemSource::id() const @@ -133,10 +157,10 @@ void StatusNotifierItemSource::contextMenu(int x, int y) { if (m_menuImporter) { // Popup menu - if (m_menuImporter->menu()) { - m_menuImporter->updateMenu(); + m_menuImporter->updateMenu(); + + if (m_menuImporter->menu()) m_menuImporter->menu()->popup(QPoint(x, y)); - } } else { qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()"; if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { @@ -245,6 +269,11 @@ void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call) } else { m_menuImporter = new MenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, this); + connect(m_menuImporter, &MenuImporter::menuUpdated, this, [this](QMenu *menu) { + if (menu == m_menuImporter->menu()) { + contextMenuReady(); + } + }); } } } diff --git a/src/systemtray/statusnotifieritemsource.h b/src/systemtray/statusnotifieritemsource.h index 166fdca..6bde5be 100644 --- a/src/systemtray/statusnotifieritemsource.h +++ b/src/systemtray/statusnotifieritemsource.h @@ -1,3 +1,25 @@ +/*************************************************************************** + * * + * Copyright (C) 2021 Reion Wong * + * Copyright (C) 2009 Marco Martin * + * Copyright (C) 2009 Matthieu Gallien * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + #ifndef STATUSNOTIFIERITEMSOURCE_H #define STATUSNOTIFIERITEMSOURCE_H diff --git a/src/systemtray/statusnotifierwatcher.cpp b/src/systemtray/statusnotifierwatcher.cpp index 90f501d..ba6ffca 100644 --- a/src/systemtray/statusnotifierwatcher.cpp +++ b/src/systemtray/statusnotifierwatcher.cpp @@ -1,3 +1,25 @@ +/*************************************************************************** + * * + * Copyright (C) 2021 Reion Wong * + * Copyright (C) 2009 Marco Martin * + * Copyright (C) 2009 Matthieu Gallien * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + #include "statusnotifierwatcher.h" #include "statusnotifieritem_interface.h" #include "statusnotifierwatcheradaptor.h" diff --git a/src/systemtray/statusnotifierwatcher.h b/src/systemtray/statusnotifierwatcher.h index 7376f8e..c357f2b 100644 --- a/src/systemtray/statusnotifierwatcher.h +++ b/src/systemtray/statusnotifierwatcher.h @@ -1,3 +1,25 @@ +/*************************************************************************** + * * + * Copyright (C) 2021 Reion Wong * + * Copyright (C) 2009 Marco Martin * + * Copyright (C) 2009 Matthieu Gallien * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + #ifndef STATUSNOTIFIERWATCHER_H #define STATUSNOTIFIERWATCHER_H diff --git a/src/systemtray/systemtraymodel.cpp b/src/systemtray/systemtraymodel.cpp index 1bd22c5..c6dc9e0 100644 --- a/src/systemtray/systemtraymodel.cpp +++ b/src/systemtray/systemtraymodel.cpp @@ -1,3 +1,23 @@ +/*************************************************************************** + * Copyright (C) 2021 Reion Wong * + * Copyright (C) 2020 Konrad Materka * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + #include "systemtraymodel.h" #include diff --git a/src/systemtray/systemtraymodel.h b/src/systemtray/systemtraymodel.h index 3142e2c..291778b 100644 --- a/src/systemtray/systemtraymodel.h +++ b/src/systemtray/systemtraymodel.h @@ -1,3 +1,23 @@ +/*************************************************************************** + * Copyright (C) 2021 Reion Wong * + * Copyright (C) 2020 Konrad Materka * + * * + * 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 2 of the License, or * + * (at your option) 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, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * + ***************************************************************************/ + #ifndef SYSTEMTRAYMODEL_H #define SYSTEMTRAYMODEL_H