You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
statusbar/src/systemtray/statusnotifieritemsource.cpp

348 lines
12 KiB
C++

/***************************************************************************
* *
* Copyright (C) 2021 Reion Wong <aj@cutefishos.com> *
* Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
* Copyright (C) 2009 Matthieu Gallien <matthieu_gallien@yahoo.fr> *
* *
* 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 <QDebug>
#include <netinet/in.h>
class TrayMenuImporter : public DBusMenuImporter
{
public:
using DBusMenuImporter::DBusMenuImporter;
protected:
QIcon iconForName(const QString & name) override {
return QIcon::fromTheme(name);
}
};
StatusNotifierItemSource::StatusNotifierItemSource(const QString &notifierItemId, QObject *parent)
: QObject(parent)
, m_menuImporter(nullptr)
, m_refreshing(false)
, m_needsReRefreshing(false)
, m_titleUpdate(true)
, m_iconUpdate(true)
, m_tooltipUpdate(true)
, m_statusUpdate(true)
, m_id(notifierItemId)
{
setObjectName(notifierItemId);
qDBusRegisterMetaType<KDbusImageStruct>();
qDBusRegisterMetaType<KDbusImageVector>();
qDBusRegisterMetaType<KDbusToolTipStruct>();
m_name = notifierItemId;
int slash = notifierItemId.indexOf('/');
if (slash == -1) {
qWarning() << "Invalid notifierItemId:" << notifierItemId;
m_valid = false;
m_statusNotifierItemInterface = nullptr;
return;
}
QString service = notifierItemId.left(slash);
QString path = notifierItemId.mid(slash);
m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, QDBusConnection::sessionBus(), this);
m_refreshTimer.setSingleShot(true);
m_refreshTimer.setInterval(10);
connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh);
m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid();
if (m_valid) {
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewTitle, this, &StatusNotifierItemSource::refreshTitle);
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewIcon, this, &StatusNotifierItemSource::refreshIcons);
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons);
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons);
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip);
connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewStatus, this, &StatusNotifierItemSource::syncStatus);
refresh();
}
}
StatusNotifierItemSource::~StatusNotifierItemSource()
{
if (m_statusNotifierItemInterface)
delete m_statusNotifierItemInterface;
}
QString StatusNotifierItemSource::id() const
{
return m_id;
}
QString StatusNotifierItemSource::title() const
{
return m_title;
}
QString StatusNotifierItemSource::tooltip() const
{
return m_tooltip;
}
QString StatusNotifierItemSource::subtitle() const
{
return m_subTitle;
}
QString StatusNotifierItemSource::iconName() const
{
return m_iconName;
}
QIcon StatusNotifierItemSource::icon() const
{
return m_icon;
}
void StatusNotifierItemSource::activate(int x, int y)
{
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(),
m_statusNotifierItemInterface->path(),
m_statusNotifierItemInterface->interface(),
QStringLiteral("Activate"));
message << x << y;
QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::activateCallback);
}
}
void StatusNotifierItemSource::secondaryActivate(int x, int y)
{
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y);
}
}
void StatusNotifierItemSource::scroll(int delta, const QString &direction)
{
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Scroll"), delta, direction);
}
}
void StatusNotifierItemSource::contextMenu(int x, int y)
{
if (m_menuImporter) {
// Popup menu
if (m_menuImporter->menu()) {
m_menuImporter->updateMenu();
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()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y);
}
}
}
void StatusNotifierItemSource::contextMenuReady()
{
if (m_menuImporter && m_menuImporter->menu()) {
emit contextMenuReady(m_menuImporter->menu());
}
}
void StatusNotifierItemSource::refreshTitle()
{
m_titleUpdate = true;
refresh();
}
void StatusNotifierItemSource::refreshIcons()
{
m_iconUpdate = true;
refresh();
}
void StatusNotifierItemSource::refreshToolTip()
{
m_tooltipUpdate = true;
refresh();
}
void StatusNotifierItemSource::refresh()
{
if (!m_refreshTimer.isActive()) {
m_refreshTimer.start();
}
}
void StatusNotifierItemSource::performRefresh()
{
if (m_refreshing) {
m_needsReRefreshing = true;
return;
}
m_refreshing = true;
QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(),
m_statusNotifierItemInterface->path(),
QStringLiteral("org.freedesktop.DBus.Properties"),
QStringLiteral("GetAll"));
message << m_statusNotifierItemInterface->interface();
QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback);
}
void StatusNotifierItemSource::syncStatus(QString)
{
}
void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call)
{
m_refreshing = false;
if (m_needsReRefreshing) {
m_needsReRefreshing = false;
performRefresh();
call->deleteLater();
return;
}
QDBusPendingReply<QVariantMap> reply = *call;
if (reply.isError()) {
m_valid = false;
} else {
QVariantMap properties = reply.argumentAt<0>();
QString path = properties[QStringLiteral("IconThemePath")].toString();
m_title = properties[QStringLiteral("Title")].toString();
m_iconName = properties[QStringLiteral("IconName")].toString();
// Reion: For icon theme path
QString iconThemePath = properties[QStringLiteral("IconThemePath")].toString();
if (!iconThemePath.isEmpty()) {
QIcon::setFallbackSearchPaths(QStringList() << iconThemePath);
}
// ToolTip
KDbusToolTipStruct toolTip;
properties[QStringLiteral("ToolTip")].value<QDBusArgument>() >> toolTip;
m_tooltip = toolTip.title;
// Use ID as an alternative :)
if (m_tooltip.isEmpty() && m_title.isEmpty()) {
m_tooltip = properties[QStringLiteral("Id")].toString();
}
// Icon
KDbusImageVector image;
properties[QStringLiteral("IconPixmap")].value<QDBusArgument>() >> image;
if (!image.isEmpty()) {
m_icon = imageVectorToPixmap(image);
}
// Menu
if (!m_menuImporter) {
QString menuObjectPath = properties[QStringLiteral("Menu")].value<QDBusObjectPath>().path();
if (!menuObjectPath.isEmpty()) {
if (menuObjectPath.startsWith(QLatin1String("/NO_DBUSMENU"))) {
// This is a hack to make it possible to disable DBusMenu in an
// application. The string "/NO_DBUSMENU" must be the same as in
// KStatusNotifierItem::setContextMenu().
qWarning() << "DBusMenu disabled for this application";
} else {
m_menuImporter = new TrayMenuImporter(m_statusNotifierItemInterface->service(),
menuObjectPath, this);
connect(m_menuImporter, &TrayMenuImporter::menuUpdated, this, [this](QMenu *menu) {
if (menu == m_menuImporter->menu()) {
contextMenuReady();
}
});
}
}
}
// qDebug() << newTitle << newIconName << newToolTip << image.isEmpty();
emit updated(this);
}
call->deleteLater();
}
void StatusNotifierItemSource::activateCallback(QDBusPendingCallWatcher *call)
{
QDBusPendingReply<void> reply = *call;
emit activateResult(!reply.isError());
call->deleteLater();
}
QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const
{
// swap from network byte order if we are little endian
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
uint *uintBuf = (uint *)image.data.data();
for (uint i = 0; i < image.data.size() / sizeof(uint); ++i) {
*uintBuf = ntohl(*uintBuf);
++uintBuf;
}
}
if (image.width == 0 || image.height == 0) {
return QPixmap();
}
// avoid a deep copy of the image data
// we need to keep a reference to the image.data alive for the lifespan of the image, even if the image is copied
// we create a new QByteArray with a shallow copy of the original data on the heap, then delete this in the QImage cleanup
auto dataRef = new QByteArray(image.data);
QImage iconImage(
reinterpret_cast<const uchar *>(dataRef->data()),
image.width,
image.height,
QImage::Format_ARGB32,
[](void *ptr) {
delete static_cast<QByteArray *>(ptr);
},
dataRef);
return QPixmap::fromImage(iconImage);
}
QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const
{
QIcon icon;
for (int i = 0; i < vector.size(); ++i) {
icon.addPixmap(KDbusImageStructToPixmap(vector[i]));
}
return icon;
}