File indexing completed on 2025-01-12 03:40:51

0001 /* This file is part of the dbusmenu-qt library
0002    SPDX-FileCopyrightText: 2010 Canonical
0003    Author: Aurelien Gateau <aurelien.gateau@canonical.com>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 #include "dbusmenuexporterdbus_p.h"
0008 
0009 // Qt
0010 #include <QDBusMessage>
0011 #include <QMenu>
0012 #include <QVariant>
0013 
0014 // Local
0015 #include "dbusmenuadaptor.h"
0016 #include "dbusmenuexporterprivate_p.h"
0017 #include "dbusmenushortcut_p.h"
0018 #include "debug_p.h"
0019 
0020 static const char *DBUSMENU_INTERFACE = "com.canonical.dbusmenu";
0021 static const char *FDO_PROPERTIES_INTERFACE = "org.freedesktop.DBus.Properties";
0022 
0023 DBusMenuExporterDBus::DBusMenuExporterDBus(DBusMenuExporter *exporter)
0024     : QObject(exporter)
0025     , m_exporter(exporter)
0026     , m_status(QStringLiteral("normal"))
0027 {
0028     DBusMenuTypes_register();
0029     new DbusmenuAdaptor(this);
0030 }
0031 
0032 uint DBusMenuExporterDBus::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, DBusMenuLayoutItem &item)
0033 {
0034     QMenu *menu = m_exporter->d->menuForId(parentId);
0035     DMRETURN_VALUE_IF_FAIL(menu, 0);
0036 
0037     // Process pending actions, we need them *now*
0038     QMetaObject::invokeMethod(m_exporter, "doUpdateActions");
0039     m_exporter->d->fillLayoutItem(&item, menu, parentId, recursionDepth, propertyNames);
0040 
0041     return m_exporter->d->m_revision;
0042 }
0043 
0044 void DBusMenuExporterDBus::Event(int id, const QString &eventType, const QDBusVariant & /*data*/, uint /*timestamp*/)
0045 {
0046     if (eventType == QStringLiteral("clicked")) {
0047         QAction *action = m_exporter->d->m_actionForId.value(id);
0048         if (!action) {
0049             return;
0050         }
0051         // dbusmenu-glib seems to ignore the Q_NOREPLY and blocks when calling
0052         // Event(), so trigger the action asynchronously
0053         QMetaObject::invokeMethod(action, "trigger", Qt::QueuedConnection);
0054     } else if (eventType == QStringLiteral("hovered")) {
0055         QMenu *menu = m_exporter->d->menuForId(id);
0056         if (menu) {
0057             QMetaObject::invokeMethod(menu, "aboutToShow");
0058         }
0059     }
0060 }
0061 
0062 QDBusVariant DBusMenuExporterDBus::GetProperty(int id, const QString &name)
0063 {
0064     QAction *action = m_exporter->d->m_actionForId.value(id);
0065     DMRETURN_VALUE_IF_FAIL(action, QDBusVariant());
0066     return QDBusVariant(m_exporter->d->m_actionProperties.value(action).value(name));
0067 }
0068 
0069 QVariantMap DBusMenuExporterDBus::getProperties(int id, const QStringList &names) const
0070 {
0071     if (id == 0) {
0072         QVariantMap map;
0073         map.insert(QStringLiteral("children-display"), QStringLiteral("submenu"));
0074         return map;
0075     }
0076     QAction *action = m_exporter->d->m_actionForId.value(id);
0077     DMRETURN_VALUE_IF_FAIL(action, QVariantMap());
0078     const QVariantMap all = m_exporter->d->m_actionProperties.value(action);
0079     if (names.isEmpty()) {
0080         return all;
0081     } else {
0082         QVariantMap map;
0083         for (const QString &name : names) {
0084             QVariant value = all.value(name);
0085             if (value.isValid()) {
0086                 map.insert(name, value);
0087             }
0088         }
0089         return map;
0090     }
0091 }
0092 
0093 DBusMenuItemList DBusMenuExporterDBus::GetGroupProperties(const QList<int> &ids, const QStringList &names)
0094 {
0095     DBusMenuItemList list;
0096     for (int id : ids) {
0097         DBusMenuItem item;
0098         item.id = id;
0099         item.properties = getProperties(item.id, names);
0100         list << item;
0101     }
0102     return list;
0103 }
0104 
0105 /**
0106  * An helper class for ::AboutToShow, which sets mChanged to true if a menu
0107  * changes after its aboutToShow() signal has been emitted.
0108  */
0109 class ActionEventFilter : public QObject
0110 {
0111 public:
0112     ActionEventFilter()
0113     {
0114     }
0115 
0116     bool mChanged = false;
0117 
0118 protected:
0119     bool eventFilter(QObject *object, QEvent *event) override
0120     {
0121         switch (event->type()) {
0122         case QEvent::ActionAdded:
0123         case QEvent::ActionChanged:
0124         case QEvent::ActionRemoved:
0125             mChanged = true;
0126             // We noticed a change, no need to filter anymore
0127             object->removeEventFilter(this);
0128             break;
0129         default:
0130             break;
0131         }
0132         return false;
0133     }
0134 };
0135 
0136 bool DBusMenuExporterDBus::AboutToShow(int id)
0137 {
0138     QMenu *menu = m_exporter->d->menuForId(id);
0139     DMRETURN_VALUE_IF_FAIL(menu, false);
0140 
0141     ActionEventFilter filter;
0142     menu->installEventFilter(&filter);
0143     QMetaObject::invokeMethod(menu, "aboutToShow");
0144     return filter.mChanged;
0145 }
0146 
0147 void DBusMenuExporterDBus::setStatus(const QString &status)
0148 {
0149     if (m_status == status) {
0150         return;
0151     }
0152     m_status = status;
0153 
0154     QVariantMap map;
0155     map.insert(QStringLiteral("Status"), QVariant(status));
0156 
0157     QDBusMessage msg =
0158         QDBusMessage::createSignal(m_exporter->d->m_objectPath, QString::fromLatin1(FDO_PROPERTIES_INTERFACE), QStringLiteral("PropertiesChanged"));
0159     QVariantList args = QVariantList() << QString::fromLatin1(DBUSMENU_INTERFACE) << map << QStringList() // New properties: none
0160         ;
0161     msg.setArguments(args);
0162     QDBusConnection::sessionBus().send(msg);
0163 }
0164 
0165 QString DBusMenuExporterDBus::status() const
0166 {
0167     return m_status;
0168 }
0169 
0170 #include "moc_dbusmenuexporterdbus_p.cpp"