File indexing completed on 2024-05-12 05:37:25

0001 /*
0002     SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "actions.h"
0008 
0009 #include "debug.h"
0010 
0011 #include <QDBusConnection>
0012 #include <QDBusPendingCallWatcher>
0013 #include <QDBusPendingReply>
0014 #include <QDebug>
0015 
0016 Actions::Actions(const QString &serviceName, const QString &objectPath, QObject *parent)
0017     : QObject(parent)
0018     , m_interface(new OrgGtkActionsInterface(serviceName, objectPath, QDBusConnection::sessionBus(), this))
0019 {
0020     connect(m_interface.get(), &OrgGtkActionsInterface::Changed, this, &Actions::onActionsChanged);
0021 }
0022 
0023 Actions::~Actions() = default;
0024 
0025 void Actions::load()
0026 {
0027     QDBusPendingReply<GMenuActionMap> reply = m_interface->DescribeAll();
0028     auto *watcher = new QDBusPendingCallWatcher(reply, this);
0029     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
0030         QDBusPendingReply<GMenuActionMap> reply = *watcher;
0031         if (reply.isError()) {
0032             qCWarning(DBUSMENUPROXY) << "Failed to get actions from" << m_interface->service() << "at" << m_interface->path() << reply.error();
0033             Q_EMIT failedToLoad();
0034         } else {
0035             m_actions = reply.value();
0036             Q_EMIT loaded();
0037         }
0038         watcher->deleteLater();
0039     });
0040 }
0041 
0042 bool Actions::get(const QString &name, GMenuAction &action) const
0043 {
0044     auto it = m_actions.find(name);
0045     if (it == m_actions.constEnd()) {
0046         return false;
0047     }
0048 
0049     action = *it;
0050     return true;
0051 }
0052 
0053 GMenuActionMap Actions::getAll() const
0054 {
0055     return m_actions;
0056 }
0057 
0058 void Actions::trigger(const QString &name, const QVariant &target, uint timestamp)
0059 {
0060     if (!m_actions.contains(name)) {
0061         qCWarning(DBUSMENUPROXY) << "Cannot invoke action" << name << "which doesn't exist";
0062         return;
0063     }
0064 
0065     QVariantList parameter;
0066     if (target.isValid()) {
0067         parameter << target;
0068     }
0069 
0070     QVariantMap platformData;
0071     if (timestamp) {
0072         // From documentation:
0073         // If the startup notification id is not available, this can be just "_TIMEtime", where
0074         // time is the time stamp from the event triggering the call.
0075         // see also gtkwindow.c extract_time_from_startup_id and startup_id_is_fake
0076         platformData.insert(QStringLiteral("desktop-startup-id"), QStringLiteral("_TIME") + QString::number(timestamp));
0077     }
0078 
0079     QDBusPendingReply<void> reply = m_interface->Activate(name, parameter, platformData);
0080     auto *watcher = new QDBusPendingCallWatcher(reply, this);
0081     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, name](QDBusPendingCallWatcher *watcher) {
0082         QDBusPendingReply<void> reply = *watcher;
0083         if (reply.isError()) {
0084             qCWarning(DBUSMENUPROXY) << "Failed to invoke action" << name << "on" << m_interface->service() << "at" << m_interface->path() << reply.error();
0085         }
0086         watcher->deleteLater();
0087     });
0088 }
0089 
0090 bool Actions::isValid() const
0091 {
0092     return !m_actions.isEmpty();
0093 }
0094 
0095 void Actions::onActionsChanged(const QStringList &removed, const StringBoolMap &enabledChanges, const QVariantMap &stateChanges, const GMenuActionMap &added)
0096 {
0097     // Collect the actions that we removed, altered, or added, so we can eventually signal changes for all menus that contain one of those actions
0098     QStringList dirtyActions;
0099 
0100     // TODO I bet for most of the loops below we could use a nice short std algorithm
0101 
0102     for (const QString &removedAction : removed) {
0103         if (m_actions.remove(removedAction)) {
0104             dirtyActions.append(removedAction);
0105         }
0106     }
0107 
0108     for (auto it = enabledChanges.constBegin(), end = enabledChanges.constEnd(); it != end; ++it) {
0109         const QString &actionName = it.key();
0110         const bool enabled = it.value();
0111 
0112         auto actionIt = m_actions.find(actionName);
0113         if (actionIt == m_actions.end()) {
0114             qCInfo(DBUSMENUPROXY) << "Got enabled changed for action" << actionName << "which we don't know";
0115             continue;
0116         }
0117 
0118         GMenuAction &action = *actionIt;
0119         if (action.enabled != enabled) {
0120             action.enabled = enabled;
0121             dirtyActions.append(actionName);
0122         } else {
0123             qCInfo(DBUSMENUPROXY) << "Got enabled change for action" << actionName << "which didn't change it";
0124         }
0125     }
0126 
0127     for (auto it = stateChanges.constBegin(), end = stateChanges.constEnd(); it != end; ++it) {
0128         const QString &actionName = it.key();
0129         const QVariant &state = it.value();
0130 
0131         auto actionIt = m_actions.find(actionName);
0132         if (actionIt == m_actions.end()) {
0133             qCInfo(DBUSMENUPROXY) << "Got state changed for action" << actionName << "which we don't know";
0134             continue;
0135         }
0136 
0137         GMenuAction &action = *actionIt;
0138 
0139         if (action.state.isEmpty()) {
0140             qCDebug(DBUSMENUPROXY) << "Got new state for action" << actionName << "that didn't have any state before";
0141             action.state.append(state);
0142             dirtyActions.append(actionName);
0143         } else {
0144             // Action state is a list but the state change only sends us a single variant, so just overwrite the first one
0145             QVariant &firstState = action.state.first();
0146             if (firstState != state) {
0147                 firstState = state;
0148                 dirtyActions.append(actionName);
0149             } else {
0150                 qCInfo(DBUSMENUPROXY) << "Got state change for action" << actionName << "which didn't change it";
0151             }
0152         }
0153     }
0154 
0155     // unite() will result in keys being present multiple times, do it manually and overwrite existing ones
0156     for (auto it = added.constBegin(), end = added.constEnd(); it != end; ++it) {
0157         const QString &actionName = it.key();
0158 
0159         if (DBUSMENUPROXY().isInfoEnabled()) {
0160             if (m_actions.contains(actionName)) {
0161                 qCInfo(DBUSMENUPROXY) << "Got new action" << actionName << "that we already have, overwriting existing one";
0162             }
0163         }
0164 
0165         m_actions.insert(actionName, it.value());
0166 
0167         dirtyActions.append(actionName);
0168     }
0169 
0170     if (!dirtyActions.isEmpty()) {
0171         Q_EMIT actionsChanged(dirtyActions);
0172     }
0173 }