1
/***************************************************************************
3
* Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
4
* Copyright (C) 2009 Matthieu Gallien <matthieu_gallien@yahoo.fr> *
6
* This program is free software; you can redistribute it and/or modify *
7
* it under the terms of the GNU General Public License as published by *
8
* the Free Software Foundation; either version 2 of the License, or *
9
* (at your option) any later version. *
11
* This program is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU General Public License for more details. *
16
* You should have received a copy of the GNU General Public License *
17
* along with this program; if not, write to the *
18
* Free Software Foundation, Inc., *
19
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
20
***************************************************************************/
22
#include "statusnotifieritemsource.h"
23
#include "systemtraytypes.h"
24
#include "statusnotifieritemservice.h"
26
#include <QApplication>
27
#include <QDesktopWidget>
31
#include <KIconLoader>
32
#include <KStandardDirs>
34
#include <QDBusMessage>
35
#include <QDBusPendingCall>
36
#include <QDBusPendingReply>
37
#include <QVariantMap>
43
#include <netinet/in.h>
45
#include <dbusmenuimporter.h>
46
#ifndef DBUSMENUQT_VERSION
47
// DBUSMENUQT_VERSION was introduced in DBusMenuQt 0.4.0
48
#define DBUSMENUQT_VERSION 0x000305
51
class PlasmaDBusMenuImporter : public DBusMenuImporter
54
PlasmaDBusMenuImporter(const QString &service, const QString &path, KIconLoader *iconLoader, QObject *parent)
55
: DBusMenuImporter(service, path, parent)
56
, m_iconLoader(iconLoader)
60
virtual QIcon iconForName(const QString &name)
62
return KIcon(name, m_iconLoader);
66
KIconLoader *m_iconLoader;
69
StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId, QObject *parent)
70
: Plasma::DataContainer(parent),
71
m_customIconLoader(0),
74
m_needsReRefreshing(false),
77
m_tooltipUpdate(true),
80
setObjectName(notifierItemId);
81
qDBusRegisterMetaType<KDbusImageStruct>();
82
qDBusRegisterMetaType<KDbusImageVector>();
83
qDBusRegisterMetaType<KDbusToolTipStruct>();
85
m_typeId = notifierItemId;
86
m_name = notifierItemId;
88
int slash = notifierItemId.indexOf('/');
90
kError() << "Invalid notifierItemId:" << notifierItemId;
92
m_statusNotifierItemInterface = 0;
95
QString service = notifierItemId.left(slash);
96
QString path = notifierItemId.mid(slash);
98
m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path,
99
QDBusConnection::sessionBus(), this);
101
m_refreshTimer.setSingleShot(true);
102
m_refreshTimer.setInterval(10);
103
connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(performRefresh()));
105
m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid();
107
connect(m_statusNotifierItemInterface, SIGNAL(NewTitle()), this, SLOT(refreshTitle()));
108
connect(m_statusNotifierItemInterface, SIGNAL(NewIcon()), this, SLOT(refreshIcons()));
109
connect(m_statusNotifierItemInterface, SIGNAL(NewAttentionIcon()), this, SLOT(refreshIcons()));
110
connect(m_statusNotifierItemInterface, SIGNAL(NewOverlayIcon()), this, SLOT(refreshIcons()));
111
connect(m_statusNotifierItemInterface, SIGNAL(NewToolTip()), this, SLOT(refreshToolTip()));
112
connect(m_statusNotifierItemInterface, SIGNAL(NewStatus(QString)), this, SLOT(syncStatus(QString)));
117
StatusNotifierItemSource::~StatusNotifierItemSource()
119
delete m_statusNotifierItemInterface;
122
KIconLoader *StatusNotifierItemSource::iconLoader() const
124
return m_customIconLoader ? m_customIconLoader : KIconLoader::global();
127
Plasma::Service *StatusNotifierItemSource::createService()
129
return new StatusNotifierItemService(this);
132
void StatusNotifierItemSource::syncStatus(QString status)
134
setData("TitleChanged", false);
135
setData("IconsChanged", false);
136
setData("TooltipChanged", false);
137
setData("StatusChanged", true);
138
setData("Status", status);
142
void StatusNotifierItemSource::refreshTitle()
144
m_titleUpdate = true;
148
void StatusNotifierItemSource::refreshIcons()
154
void StatusNotifierItemSource::refreshToolTip()
156
m_tooltipUpdate = true;
160
void StatusNotifierItemSource::refresh()
162
if (!m_refreshTimer.isActive()) {
163
m_refreshTimer.start();
167
void StatusNotifierItemSource::performRefresh()
170
m_needsReRefreshing = true;
175
QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(),
176
m_statusNotifierItemInterface->path(), "org.freedesktop.DBus.Properties", "GetAll");
178
message << m_statusNotifierItemInterface->interface();
179
QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message);
180
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
181
connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, SLOT(refreshCallback(QDBusPendingCallWatcher *)));
185
\todo add a smart pointer to guard call and to automatically delete it at the end of the function
187
void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call)
189
m_refreshing = false;
190
if (m_needsReRefreshing) {
191
m_needsReRefreshing = false;
197
QDBusPendingReply<QVariantMap> reply = *call;
198
if (reply.isError()) {
201
// record what has changed
202
setData("TitleChanged", m_titleUpdate);
203
m_titleUpdate = false;
204
setData("IconsChanged", m_iconUpdate);
205
m_iconUpdate = false;
206
setData("ToolTipChanged", m_tooltipUpdate);
207
m_tooltipUpdate = false;
208
setData("StatusChanged", m_statusUpdate);
209
m_statusUpdate = false;
211
//IconThemePath (handle this one first, because it has an impact on
213
QVariantMap properties = reply.argumentAt<0>();
214
if (!m_customIconLoader) {
215
QString path = properties["IconThemePath"].toString();
216
if (!path.isEmpty()) {
217
// FIXME: If last part of path is not "icons", this won't work!
218
QStringList tokens = path.split('/', QString::SkipEmptyParts);
219
if (tokens.length() >= 3 && tokens.takeLast() == "icons") {
220
QString appName = tokens.takeLast();
221
QString prefix = '/' + tokens.join("/");
222
// FIXME: Fix KIconLoader and KIconTheme so that we can use
223
// our own instance of KStandardDirs
224
KGlobal::dirs()->addResourceDir("data", prefix);
225
// We use a separate instance of KIconLoader to avoid
226
// adding all application dirs to KIconLoader::global(), to
227
// avoid potential icon name clashes between application
229
m_customIconLoader = new KIconLoader(appName, 0 /* dirs */, this);
231
kWarning() << "Wrong IconThemePath" << path << ": too short or does not end with 'icons'";
236
setData("Category", properties["Category"]);
237
setData("Status", properties["Status"]);
238
setData("Title", properties["Title"]);
239
setData("Id", properties["Id"]);
240
setData("WindowId", properties["WindowId"]);
241
setData("ItemIsMenu", properties["ItemIsMenu"]);
244
setData("AttentionMovieName", properties["AttentionMovieName"]);
247
QStringList overlayNames;
251
KDbusImageVector image;
254
properties["OverlayIconPixmap"].value<QDBusArgument>() >> image;
255
if (image.isEmpty()) {
256
QString iconName = properties["OverlayIconName"].toString();
257
setData("OverlayIconName", iconName);
258
if (!iconName.isEmpty()) {
259
overlayNames << iconName;
260
overlay = KIcon(iconName, iconLoader());
263
overlay = imageVectorToPixmap(image);
266
properties["IconPixmap"].value<QDBusArgument>() >> image;
267
if (image.isEmpty()) {
268
QString iconName = properties["IconName"].toString();
269
setData("IconName", iconName);
270
if (!iconName.isEmpty()) {
271
icon = KIcon(iconName, iconLoader(), overlayNames);
273
if (overlayNames.isEmpty() && !overlay.isNull()) {
274
overlayIcon(&icon, &overlay);
278
icon = imageVectorToPixmap(image);
279
if (!icon.isNull() && !overlay.isNull()) {
280
overlayIcon(&icon, &overlay);
283
setData("Icon", icon);
288
KDbusImageVector image;
291
properties["AttentionIconPixmap"].value<QDBusArgument>() >> image;
292
if (image.isEmpty()) {
293
QString iconName = properties["AttentionIconName"].toString();
294
setData("AttentionIconName", iconName);
295
if (!iconName.isEmpty()) {
296
attentionIcon = KIcon(iconName, iconLoader(), overlayNames);
298
if (overlayNames.isEmpty() && !overlay.isNull()) {
299
overlayIcon(&attentionIcon, &overlay);
303
attentionIcon = imageVectorToPixmap(image);
304
if (!attentionIcon.isNull() && !overlay.isNull()) {
305
overlayIcon(&attentionIcon, &overlay);
308
setData("AttentionIcon", attentionIcon);
313
KDbusToolTipStruct toolTip;
314
properties["ToolTip"].value<QDBusArgument>() >> toolTip;
315
if (toolTip.title.isEmpty()) {
316
setData("ToolTipTitle", QVariant());
317
setData("ToolTipSubTitle", QVariant());
318
setData("ToolTipIcon", QVariant());
321
if (toolTip.image.size() == 0) {
322
toolTipIcon = KIcon(toolTip.icon, iconLoader());
324
toolTipIcon = imageVectorToPixmap(toolTip.image);
326
setData("ToolTipTitle", toolTip.title);
327
setData("ToolTipSubTitle", toolTip.subTitle);
328
setData("ToolTipIcon", toolTipIcon);
333
if (!m_menuImporter) {
334
QString menuObjectPath = properties["Menu"].value<QDBusObjectPath>().path();
335
if (!menuObjectPath.isEmpty()) {
336
if (menuObjectPath == "/NO_DBUSMENU") {
337
// This is a hack to make it possible to disable DBusMenu in an
338
// application. The string "/NO_DBUSMENU" must be the same as in
339
// KStatusNotifierItem::setContextMenu().
340
kWarning() << "DBusMenu disabled for this application";
342
m_menuImporter = new PlasmaDBusMenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, iconLoader(), this);
343
#if DBUSMENUQT_VERSION >= 0x000400
344
connect(m_menuImporter, SIGNAL(menuUpdated()), this, SLOT(contextMenuReady()));
355
void StatusNotifierItemSource::contextMenuReady()
357
#if DBUSMENUQT_VERSION < 0x000400
358
// Work around to avoid infinite recursion because menuReadyToBeShown() is emitted
359
// by DBusMenuImporter at the end of its slot connected to aboutToShow()
360
// (dbusmenu-qt 0.3.5)
361
disconnect(m_menuImporter, SIGNAL(menuReadyToBeShown()), this, SLOT(contextMenuReady()));
363
emit contextMenuReady(m_menuImporter->menu());
366
QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const
368
//swap from network byte order if we are little endian
369
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
370
uint *uintBuf = (uint *) image.data.data();
371
for (uint i = 0; i < image.data.size()/sizeof(uint); ++i) {
372
*uintBuf = ntohl(*uintBuf);
376
QImage iconImage(image.width, image.height, QImage::Format_ARGB32 );
377
memcpy(iconImage.bits(), (uchar*)image.data.data(), iconImage.numBytes());
379
return QPixmap::fromImage(iconImage);
382
QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const
386
for (int i = 0; i<vector.size(); ++i) {
387
icon.addPixmap(KDbusImageStructToPixmap(vector[i]));
393
void StatusNotifierItemSource::overlayIcon(QIcon *icon, QIcon *overlay)
396
QPixmap m_iconPixmap = icon->pixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall);
398
QPainter p(&m_iconPixmap);
400
const int size = KIconLoader::SizeSmall/2;
401
p.drawPixmap(QRect(size, size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
403
tmp.addPixmap(m_iconPixmap);
405
//if an m_icon exactly that size wasn't found don't add it to the vector
406
m_iconPixmap = icon->pixmap(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium);
407
if (m_iconPixmap.width() == KIconLoader::SizeSmallMedium) {
408
const int size = KIconLoader::SizeSmall/2;
409
QPainter p(&m_iconPixmap);
410
p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
412
tmp.addPixmap(m_iconPixmap);
415
m_iconPixmap = icon->pixmap(KIconLoader::SizeMedium, KIconLoader::SizeMedium);
416
if (m_iconPixmap.width() == KIconLoader::SizeMedium) {
417
const int size = KIconLoader::SizeSmall/2;
418
QPainter p(&m_iconPixmap);
419
p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
421
tmp.addPixmap(m_iconPixmap);
424
m_iconPixmap = icon->pixmap(KIconLoader::SizeLarge, KIconLoader::SizeLarge);
425
if (m_iconPixmap.width() == KIconLoader::SizeLarge) {
426
const int size = KIconLoader::SizeSmall;
427
QPainter p(&m_iconPixmap);
428
p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size));
430
tmp.addPixmap(m_iconPixmap);
433
// We can't do 'm_icon->addPixmap()' because if 'm_icon' uses KIconEngine,
434
// it will ignore the added pixmaps. This is not a bug in KIconEngine,
435
// QIcon::addPixmap() doc says: "Custom m_icon engines are free to ignore
436
// additionally added pixmaps".
438
//hopefully huge and enormous not necessary right now, since it's quite costly
441
void StatusNotifierItemSource::activate(int x, int y)
443
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
444
m_statusNotifierItemInterface->call(QDBus::NoBlock, "Activate", x, y);
448
void StatusNotifierItemSource::secondaryActivate(int x, int y)
450
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
451
m_statusNotifierItemInterface->call(QDBus::NoBlock, "SecondaryActivate", x, y);
455
void StatusNotifierItemSource::scroll(int delta, const QString &direction)
457
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
458
m_statusNotifierItemInterface->call(QDBus::NoBlock, "Scroll", delta, direction);
462
void StatusNotifierItemSource::contextMenu(int x, int y)
464
if (m_menuImporter) {
465
#if DBUSMENUQT_VERSION >= 0x000400
466
m_menuImporter->updateMenu();
468
QMenu *menu = m_menuImporter->menu();
469
// Simulate an "aboutToShow" so that menu->sizeHint() is correct. Otherwise
470
// the menu may show up over the applet if new actions are added on the
472
connect(m_menuImporter, SIGNAL(menuReadyToBeShown()), this, SLOT(contextMenuReady()));
473
QMetaObject::invokeMethod(menu, "aboutToShow");
476
kWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()";
477
if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) {
478
m_statusNotifierItemInterface->call(QDBus::NoBlock, "ContextMenu", x, y);
483
#include "statusnotifieritemsource.moc"