QortalOS Brooklyn for Raspberry Pi 4
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

198 lines
7.0 KiB

/*
SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <[email protected]>
SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "actions.h"
#include "debug.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
#include <QDebug>
#include <QStringList>
#include <QVariantList>
static const QString s_orgGtkActions = QStringLiteral("org.gtk.Actions");
Actions::Actions(const QString &serviceName, const QString &objectPath, QObject *parent)
: QObject(parent)
, m_serviceName(serviceName)
, m_objectPath(objectPath)
{
Q_ASSERT(!serviceName.isEmpty());
Q_ASSERT(!m_objectPath.isEmpty());
if (!QDBusConnection::sessionBus().connect(serviceName,
objectPath,
s_orgGtkActions,
QStringLiteral("Changed"),
this,
SLOT(onActionsChanged(QStringList, StringBoolMap, QVariantMap, GMenuActionMap)))) {
qCWarning(DBUSMENUPROXY) << "Failed to subscribe to action changes for" << parent << "on" << serviceName << "at" << objectPath;
}
}
Actions::~Actions() = default;
void Actions::load()
{
QDBusMessage msg = QDBusMessage::createMethodCall(m_serviceName, m_objectPath, s_orgGtkActions, QStringLiteral("DescribeAll"));
QDBusPendingReply<GMenuActionMap> reply = QDBusConnection::sessionBus().asyncCall(msg);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<GMenuActionMap> reply = *watcher;
if (reply.isError()) {
qCWarning(DBUSMENUPROXY) << "Failed to get actions from" << m_serviceName << "at" << m_objectPath << reply.error();
Q_EMIT failedToLoad();
} else {
m_actions = reply.value();
Q_EMIT loaded();
}
watcher->deleteLater();
});
}
bool Actions::get(const QString &name, GMenuAction &action) const
{
auto it = m_actions.find(name);
if (it == m_actions.constEnd()) {
return false;
}
action = *it;
return true;
}
GMenuActionMap Actions::getAll() const
{
return m_actions;
}
void Actions::trigger(const QString &name, const QVariant &target, uint timestamp)
{
if (!m_actions.contains(name)) {
qCWarning(DBUSMENUPROXY) << "Cannot invoke action" << name << "which doesn't exist";
return;
}
QDBusMessage msg = QDBusMessage::createMethodCall(m_serviceName, m_objectPath, s_orgGtkActions, QStringLiteral("Activate"));
msg << name;
QVariantList args;
if (target.isValid()) {
args << target;
}
msg << QVariant::fromValue(args);
QVariantMap platformData;
if (timestamp) {
// From documentation:
// If the startup notification id is not available, this can be just "_TIMEtime", where
// time is the time stamp from the event triggering the call.
// see also gtkwindow.c extract_time_from_startup_id and startup_id_is_fake
platformData.insert(QStringLiteral("desktop-startup-id"), QStringLiteral("_TIME") + QString::number(timestamp));
}
msg << platformData;
QDBusPendingReply<void> reply = QDBusConnection::sessionBus().asyncCall(msg);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, name](QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<void> reply = *watcher;
if (reply.isError()) {
qCWarning(DBUSMENUPROXY) << "Failed to invoke action" << name << "on" << m_serviceName << "at" << m_objectPath << reply.error();
}
watcher->deleteLater();
});
}
bool Actions::isValid() const
{
return !m_actions.isEmpty();
}
void Actions::onActionsChanged(const QStringList &removed, const StringBoolMap &enabledChanges, const QVariantMap &stateChanges, const GMenuActionMap &added)
{
// Collect the actions that we removed, altered, or added, so we can eventually signal changes for all menus that contain one of those actions
QStringList dirtyActions;
// TODO I bet for most of the loops below we could use a nice short std algorithm
for (const QString &removedAction : removed) {
if (m_actions.remove(removedAction)) {
dirtyActions.append(removedAction);
}
}
for (auto it = enabledChanges.constBegin(), end = enabledChanges.constEnd(); it != end; ++it) {
const QString &actionName = it.key();
const bool enabled = it.value();
auto actionIt = m_actions.find(actionName);
if (actionIt == m_actions.end()) {
qCInfo(DBUSMENUPROXY) << "Got enabled changed for action" << actionName << "which we don't know";
continue;
}
GMenuAction &action = *actionIt;
if (action.enabled != enabled) {
action.enabled = enabled;
dirtyActions.append(actionName);
} else {
qCInfo(DBUSMENUPROXY) << "Got enabled change for action" << actionName << "which didn't change it";
}
}
for (auto it = stateChanges.constBegin(), end = stateChanges.constEnd(); it != end; ++it) {
const QString &actionName = it.key();
const QVariant &state = it.value();
auto actionIt = m_actions.find(actionName);
if (actionIt == m_actions.end()) {
qCInfo(DBUSMENUPROXY) << "Got state changed for action" << actionName << "which we don't know";
continue;
}
GMenuAction &action = *actionIt;
if (action.state.isEmpty()) {
qCDebug(DBUSMENUPROXY) << "Got new state for action" << actionName << "that didn't have any state before";
action.state.append(state);
dirtyActions.append(actionName);
} else {
// Action state is a list but the state change only sends us a single variant, so just overwrite the first one
QVariant &firstState = action.state.first();
if (firstState != state) {
firstState = state;
dirtyActions.append(actionName);
} else {
qCInfo(DBUSMENUPROXY) << "Got state change for action" << actionName << "which didn't change it";
}
}
}
// unite() will result in keys being present multiple times, do it manually and overwrite existing ones
for (auto it = added.constBegin(), end = added.constEnd(); it != end; ++it) {
const QString &actionName = it.key();
if (DBUSMENUPROXY().isInfoEnabled()) {
if (m_actions.contains(actionName)) {
qCInfo(DBUSMENUPROXY) << "Got new action" << actionName << "that we already have, overwriting existing one";
}
}
m_actions.insert(actionName, it.value());
dirtyActions.append(actionName);
}
if (!dirtyActions.isEmpty()) {
Q_EMIT actionsChanged(dirtyActions);
}
}