kde-workspace/plasma/dataengines/statusnotifieritem/statusnotifieritemsource.cpp
Ivailo Monev dc50956578 plasma: remove bogus status notifier item animation feature
AttentionMovieName is not valid property of the org.kde.StatusNotifierItem
interface, see:
kdelibs/kdeui/notifications/kstatusnotifieritemdbus_p.h

it is in the interface file:
kdeui/notifications/org.kde.StatusNotifierItem.xml

but not implemented in the D-Bus interface

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-09-28 16:59:19 +03:00

472 lines
17 KiB
C++

/***************************************************************************
* *
* 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 "statusnotifieritemservice.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QIcon>
#include <KDebug>
#include <KIcon>
#include <KIconLoader>
#include <KStandardDirs>
#include <QPainter>
#include <QDBusMessage>
#include <QDBusPendingCall>
#include <QDBusPendingReply>
#include <QtCore/qvariant.h>
#include <QImage>
#include <QMenu>
#include <QPixmap>
#include <QtCore/qglobal.h>
#include <netinet/in.h>
#include <dbusmenuimporter.h>
class PlasmaDBusMenuImporter : public DBusMenuImporter
{
public:
PlasmaDBusMenuImporter(const QString &service, const QString &path, KIconLoader *iconLoader, QObject *parent)
: DBusMenuImporter(service, path, parent)
, m_iconLoader(iconLoader)
{}
protected:
virtual QIcon iconForName(const QString &name)
{
return KIcon(name, m_iconLoader);
}
private:
KIconLoader *m_iconLoader;
};
StatusNotifierItemSource::StatusNotifierItemSource(const QString &notifierItemId, QObject *parent)
: Plasma::DataContainer(parent),
m_customIconLoader(0),
m_menuImporter(0),
m_refreshing(false),
m_needsReRefreshing(false),
m_titleUpdate(true),
m_iconUpdate(true),
m_tooltipUpdate(true),
m_statusUpdate(true)
{
setObjectName(notifierItemId);
qDBusRegisterMetaType<KDbusImageStruct>();
qDBusRegisterMetaType<KDbusImageVector>();
qDBusRegisterMetaType<KDbusToolTipStruct>();
m_typeId = notifierItemId;
m_name = notifierItemId;
int slash = notifierItemId.indexOf('/');
if (slash == -1) {
kError() << "Invalid notifierItemId:" << notifierItemId;
m_valid = false;
m_statusNotifierItemInterface = 0;
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, SIGNAL(timeout()), this, SLOT(performRefresh()));
m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid();
if (m_valid) {
connect(m_statusNotifierItemInterface, SIGNAL(NewTitle()), this, SLOT(refreshTitle()));
connect(m_statusNotifierItemInterface, SIGNAL(NewIcon()), this, SLOT(refreshIcons()));
connect(m_statusNotifierItemInterface, SIGNAL(NewAttentionIcon()), this, SLOT(refreshIcons()));
connect(m_statusNotifierItemInterface, SIGNAL(NewOverlayIcon()), this, SLOT(refreshIcons()));
connect(m_statusNotifierItemInterface, SIGNAL(NewToolTip()), this, SLOT(refreshToolTip()));
connect(m_statusNotifierItemInterface, SIGNAL(NewStatus(QString)), this, SLOT(syncStatus(QString)));
refresh();
}
}
StatusNotifierItemSource::~StatusNotifierItemSource()
{
delete m_statusNotifierItemInterface;
}
KIconLoader *StatusNotifierItemSource::iconLoader() const
{
return m_customIconLoader ? m_customIconLoader : KIconLoader::global();
}
Plasma::Service *StatusNotifierItemSource::createService()
{
return new StatusNotifierItemService(this);
}
void StatusNotifierItemSource::syncStatus(QString status)
{
setData("TitleChanged", false);
setData("IconsChanged", false);
setData("TooltipChanged", false);
setData("StatusChanged", true);
setData("Status", status);
checkForUpdate();
}
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(), "org.freedesktop.DBus.Properties", "GetAll");
message << m_statusNotifierItemInterface->interface();
QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(refreshCallback(QDBusPendingCallWatcher*)));
}
/**
\todo add a smart pointer to guard call and to automatically delete it at the end of the function
*/
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 {
// record what has changed
setData("TitleChanged", m_titleUpdate);
m_titleUpdate = false;
setData("IconsChanged", m_iconUpdate);
m_iconUpdate = false;
setData("ToolTipChanged", m_tooltipUpdate);
m_tooltipUpdate = false;
setData("StatusChanged", m_statusUpdate);
m_statusUpdate = false;
//IconThemePath (handle this one first, because it has an impact on
//others)
QVariantMap properties = reply.argumentAt<0>();
if (!m_customIconLoader) {
QString path = properties["IconThemePath"].toString();
if (!path.isEmpty()) {
QStringList tokens = path.split('/', QString::SkipEmptyParts);
if (tokens.length() >= 3) {
QString appName = tokens.takeLast();
QString prefix = '/' + tokens.join("/");
// FIXME: Fix KIconLoader and KIconTheme so that we can use
// our own instance of KStandardDirs
KGlobal::dirs()->addResourceDir("data", prefix);
// We use a separate instance of KIconLoader to avoid
// adding all application dirs to KIconLoader::global(), to
// avoid potential icon name clashes between application
// icons
m_customIconLoader = new KIconLoader(appName, 0 /* dirs */, this);
} else {
kWarning() << "Wrong IconThemePath" << path << ": too short or does not end with 'icons'";
}
}
}
setData("IconThemePath", properties["IconThemePath"]);
setData("Category", properties["Category"]);
setData("Status", properties["Status"]);
setData("Title", properties["Title"]);
setData("Id", properties["Id"]);
setData("WindowId", properties["WindowId"]);
setData("ItemIsMenu", properties["ItemIsMenu"]);
QIcon overlay;
QStringList overlayNames;
//Icon
{
KDbusImageVector image;
QIcon icon;
QString iconName;
QDBusArgument argumentValue = properties["OverlayIconPixmap"].value<QDBusArgument>();
if (argumentValue.currentType() != QDBusArgument::UnknownType) {
argumentValue >> image;
}
if (image.isEmpty()) {
QString iconName = properties["OverlayIconName"].toString();
setData("OverlayIconName", iconName);
if (!iconName.isEmpty()) {
overlayNames << iconName;
overlay = KIcon(iconName, iconLoader());
}
} else {
overlay = imageVectorToPixmap(image);
}
argumentValue = properties["IconPixmap"].value<QDBusArgument>();
if (argumentValue.currentType() != QDBusArgument::UnknownType) {
argumentValue >> image;
}
if (image.isEmpty()) {
iconName = properties["IconName"].toString();
if (!iconName.isEmpty()) {
icon = KIcon(iconName, iconLoader(), overlayNames);
if (overlayNames.isEmpty() && !overlay.isNull()) {
overlayIcon(&icon, &overlay);
}
}
} else {
icon = imageVectorToPixmap(image);
if (!icon.isNull() && !overlay.isNull()) {
overlayIcon(&icon, &overlay);
}
}
setData("Icon", icon);
setData("IconName", iconName);
}
//Attention icon
{
KDbusImageVector image;
QIcon attentionIcon;
QDBusArgument argumentValue = properties["AttentionIconPixmap"].value<QDBusArgument>();
if (argumentValue.currentType() != QDBusArgument::UnknownType) {
argumentValue >> image;
}
if (image.isEmpty()) {
QString iconName = properties["AttentionIconName"].toString();
setData("AttentionIconName", iconName);
if (!iconName.isEmpty()) {
attentionIcon = KIcon(iconName, iconLoader(), overlayNames);
if (overlayNames.isEmpty() && !overlay.isNull()) {
overlayIcon(&attentionIcon, &overlay);
}
}
} else {
attentionIcon = imageVectorToPixmap(image);
if (!attentionIcon.isNull() && !overlay.isNull()) {
overlayIcon(&attentionIcon, &overlay);
}
}
setData("AttentionIcon", attentionIcon);
}
//ToolTip
{
KDbusToolTipStruct toolTip;
QDBusArgument argumentValue = properties["ToolTip"].value<QDBusArgument>();
if (argumentValue.currentType() != QDBusArgument::UnknownType) {
argumentValue >> toolTip;
}
if (toolTip.title.isEmpty()) {
setData("ToolTipTitle", QVariant());
setData("ToolTipSubTitle", QVariant());
setData("ToolTipIcon", QVariant());
} else {
QIcon toolTipIcon;
if (toolTip.image.size() == 0) {
toolTipIcon = KIcon(toolTip.icon, iconLoader());
} else {
toolTipIcon = imageVectorToPixmap(toolTip.image);
}
setData("ToolTipTitle", toolTip.title);
setData("ToolTipSubTitle", toolTip.subTitle);
setData("ToolTipIcon", toolTipIcon);
}
}
//Menu
if (!m_menuImporter) {
QString menuObjectPath = properties["Menu"].value<QDBusObjectPath>().path();
if (!menuObjectPath.isEmpty()) {
if (menuObjectPath == "/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().
kWarning() << "DBusMenu disabled for this application";
} else {
m_menuImporter = new PlasmaDBusMenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, iconLoader(), this);
connect(m_menuImporter, SIGNAL(menuUpdated()), this, SLOT(contextMenuReady()));
}
}
}
}
checkForUpdate();
call->deleteLater();
}
void StatusNotifierItemSource::contextMenuReady()
{
emit contextMenuReady(m_menuImporter->menu());
}
QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const
{
// swap from network byte order if we are little endian
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
uint *uintBuf = (uint *) image.data.data();
for (uint i = 0; i < image.data.size()/sizeof(uint); ++i) {
*uintBuf = ntohl(*uintBuf);
++uintBuf;
}
#endif
QImage iconImage(image.width, image.height, QImage::Format_ARGB32 );
memcpy(iconImage.bits(), (uchar*)image.data.data(), iconImage.byteCount());
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;
}
void StatusNotifierItemSource::overlayIcon(QIcon *icon, QIcon *overlay)
{
QIcon tmp;
QPixmap m_iconPixmap = icon->pixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall);
QPainter p(&m_iconPixmap);
const int size = KIconLoader::SizeSmall/2;
p.drawPixmap(QRect(size, size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
p.end();
tmp.addPixmap(m_iconPixmap);
//if an m_icon exactly that size wasn't found don't add it to the vector
m_iconPixmap = icon->pixmap(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium);
if (m_iconPixmap.width() == KIconLoader::SizeSmallMedium) {
const int size = KIconLoader::SizeSmall/2;
QPainter p(&m_iconPixmap);
p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
p.end();
tmp.addPixmap(m_iconPixmap);
}
m_iconPixmap = icon->pixmap(KIconLoader::SizeMedium, KIconLoader::SizeMedium);
if (m_iconPixmap.width() == KIconLoader::SizeMedium) {
const int size = KIconLoader::SizeSmall/2;
QPainter p(&m_iconPixmap);
p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
p.end();
tmp.addPixmap(m_iconPixmap);
}
m_iconPixmap = icon->pixmap(KIconLoader::SizeLarge, KIconLoader::SizeLarge);
if (m_iconPixmap.width() == KIconLoader::SizeLarge) {
const int size = KIconLoader::SizeSmall;
QPainter p(&m_iconPixmap);
p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
p.end();
tmp.addPixmap(m_iconPixmap);
}
// We can't do 'm_icon->addPixmap()' because if 'm_icon' uses KIconEngine,
// it will ignore the added pixmaps. This is not a bug in KIconEngine,
// QIcon::addPixmap() doc says: "Custom m_icon engines are free to ignore
// additionally added pixmaps".
*icon = tmp;
//hopefully huge and enormous not necessary right now, since it's quite costly
}
void StatusNotifierItemSource::activate(int x, int y)
{
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, "Activate", x, y);
}
}
void StatusNotifierItemSource::secondaryActivate(int x, int y)
{
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, "SecondaryActivate", x, y);
}
}
void StatusNotifierItemSource::scroll(int delta, const QString &direction)
{
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, "Scroll", delta, direction);
}
}
void StatusNotifierItemSource::contextMenu(int x, int y)
{
if (m_menuImporter) {
m_menuImporter->updateMenu();
} else {
kWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()";
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
m_statusNotifierItemInterface->call(QDBus::NoBlock, "ContextMenu", x, y);
}
}
}
#include "moc_statusnotifieritemsource.cpp"