26 #include <kpassivepopup.h>
27 #include <kiconloader.h>
31 #include <kcharsets.h>
37 #include <QTextDocument>
38 #include <QApplication>
39 #include <QDesktopWidget>
40 #include <QDBusConnection>
41 #include <QDBusConnectionInterface>
42 #include <QDBusServiceWatcher>
43 #include <QXmlStreamReader>
44 #include <kconfiggroup.h>
48 static const char dbusPath[] =
"/org/freedesktop/Notifications";
51 :
KNotifyPlugin(parent) , m_animationTimer(0), m_dbusServiceExists(false),
52 m_dbusServiceCapCacheDirty(true)
54 QRect screen = QApplication::desktop()->availableGeometry();
55 m_nextPosition = screen.top();
58 QDBusConnectionInterface*
interface = QDBusConnection::sessionBus().interface();
59 m_dbusServiceExists =
interface && interface->isServiceRegistered(
dbusServiceName);
61 if( m_dbusServiceExists )
65 QDBusServiceWatcher *watcher =
new QDBusServiceWatcher(
this);
66 watcher->setConnection(QDBusConnection::sessionBus());
67 watcher->setWatchMode(QDBusServiceWatcher::WatchForOwnerChange);
69 connect(watcher, SIGNAL(serviceOwnerChanged(
const QString&,
const QString&,
const QString&)),
70 SLOT(slotServiceOwnerChanged(
const QString&,
const QString&,
const QString&)));
71 if(!m_dbusServiceExists)
73 bool startfdo =
false;
77 if (qgetenv(
"KDE_FULL_SESSION").isEmpty())
79 QDBusMessage message = QDBusMessage::createMethodCall(
"org.freedesktop.DBus",
80 "/org/freedesktop/DBus",
81 "org.freedesktop.DBus",
82 "ListActivatableNames");
83 QDBusReply<QStringList> reply = QDBusConnection::sessionBus().call(message);
91 m_dbusServiceExists =
true;
96 QDBusConnection::sessionBus().interface()->startService(
dbusServiceName);
103 foreach(KPassivePopup *p,m_popups)
109 kDebug() <<
id <<
" active notifications:" << m_popups.keys() << m_idMap.keys();
111 if(m_popups.contains(
id) || m_idMap.contains(
id))
113 kDebug() <<
"the popup is already shown";
120 if(m_dbusServiceExists)
122 if(!sendNotificationDBus(
id, 0, config))
135 QString appCaption, iconName;
136 getAppCaptionAndIconName(c, &appCaption, &iconName);
138 KIconLoader iconLoader(iconName);
139 QPixmap appIcon = iconLoader.loadIcon( iconName, KIconLoader::Small );
149 KPassivePopup *pop =
new KPassivePopup( config->
winId );
151 fillPopup(pop,
id,config);
152 QRect screen = QApplication::desktop()->availableGeometry();
153 pop->setAutoDelete(
true );
154 connect(pop, SIGNAL(destroyed()) ,
this, SLOT(slotPopupDestroyed()) );
156 pop->setTimeout(timeout);
158 pop->show(QPoint(screen.left() + screen.width()/2 - pop->width()/2 , m_nextPosition));
159 m_nextPosition+=pop->height();
162 void NotifyByPopup::slotPopupDestroyed( )
167 QMap<int,KPassivePopup*>::iterator it;
168 for(it=m_popups.begin() ; it!=m_popups.end(); ++it )
174 m_popups.remove(it.key());
180 if(!m_animationTimer)
181 m_animationTimer = startTimer(10);
186 if(event->timerId() != m_animationTimer)
187 return KNotifyPlugin::timerEvent(event);
190 QRect screen = QApplication::desktop()->availableGeometry();
191 m_nextPosition = screen.top();
192 foreach(KPassivePopup *pop,m_popups)
194 int posy=pop->pos().y();
195 if(posy > m_nextPosition)
197 posy=qMax(posy-5,m_nextPosition);
198 m_nextPosition = posy + pop->height();
199 cont = cont || posy != m_nextPosition;
200 pop->move(pop->pos().x(),posy);
203 m_nextPosition += pop->height();
207 killTimer(m_animationTimer);
208 m_animationTimer = 0;
212 void NotifyByPopup::slotLinkClicked(
const QString &adr )
214 unsigned int id=adr.section(
'/' , 0 , 0).toUInt();
215 unsigned int action=adr.section(
'/' , 1 , 1).toUInt();
219 if(
id==0 || action==0)
227 delete m_popups.take(
id);
229 if( m_dbusServiceExists)
231 closeNotificationDBus(
id);
237 if (m_popups.contains(
id))
239 KPassivePopup *p=m_popups[id];
240 fillPopup(p,
id, config);
246 if( m_dbusServiceExists)
248 sendNotificationDBus(
id,
id, config);
259 void NotifyByPopup::fillPopup(KPassivePopup *pop,
int id,
KNotifyConfig * config)
261 QString appCaption, iconName;
262 getAppCaptionAndIconName(config, &appCaption, &iconName);
264 KIconLoader iconLoader(iconName);
265 QPixmap appIcon = iconLoader.loadIcon( iconName, KIconLoader::Small );
267 KVBox *vb = pop->standardView( config->
title.isEmpty() ? appCaption : config->
title , config->
image.
isNull() ? config->
text : QString() , appIcon );
272 QPixmap pix = QPixmap::fromImage(config->
image.
toImage());
273 KHBox *hb =
new KHBox(vb);
274 hb->setSpacing(KDialog::spacingHint());
275 QLabel *pil=
new QLabel(hb);
276 pil->setPixmap( pix );
277 pil->setScaledContents(
true);
278 if(pix.height() > 80 && pix.height() > pix.width() )
280 pil->setMaximumHeight(80);
281 pil->setMaximumWidth(80*pix.width()/pix.height());
283 else if(pix.width() > 80 && pix.height() <= pix.width())
285 pil->setMaximumWidth(80);
286 pil->setMaximumHeight(80*pix.height()/pix.width());
289 QLabel *msg =
new QLabel( config->
text, vb );
290 msg->setAlignment( Qt::AlignLeft );
294 if ( !config->
actions.isEmpty() )
296 QString linkCode=QString::fromLatin1(
"<p align=\"right\">");
298 foreach (
const QString & it , config->
actions )
301 linkCode+=QString::fromLatin1(
" <a href=\"%1/%2\">%3</a> ").arg(
id ).arg( i ).arg( Qt::escape(it) );
303 linkCode+=QString::fromLatin1(
"</p>");
304 QLabel *link =
new QLabel(linkCode , vb );
305 link->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
306 link->setOpenExternalLinks(
false);
308 QObject::connect(link, SIGNAL(linkActivated(
const QString &)),
this, SLOT(slotLinkClicked(
const QString& ) ) );
309 QObject::connect(link, SIGNAL(linkActivated(
const QString &)), pop, SLOT(hide()));
315 void NotifyByPopup::slotServiceOwnerChanged(
const QString & serviceName,
316 const QString & oldOwner,
const QString & newOwner )
318 kDebug() << serviceName << oldOwner << newOwner;
321 foreach (
int id, m_idMap.keys())
325 m_dbusServiceCapCacheDirty =
true;
326 m_dbusServiceCapabilities.clear();
328 if(newOwner.isEmpty())
330 m_dbusServiceExists =
false;
332 else if(oldOwner.isEmpty())
334 m_dbusServiceExists =
true;
337 bool connected = QDBusConnection::sessionBus().connect(QString(),
342 SLOT(slotDBusNotificationActionInvoked(uint,
const QString&)));
344 kWarning() <<
"warning: failed to connect to ActionInvoked dbus signal";
347 connected = QDBusConnection::sessionBus().connect(QString(),
350 "NotificationClosed",
352 SLOT(slotDBusNotificationClosed(uint,uint)));
354 kWarning() <<
"warning: failed to connect to NotificationClosed dbus signal";
360 void NotifyByPopup::slotDBusNotificationActionInvoked(uint dbus_id,
const QString& actKey)
363 int id = m_idMap.key(dbus_id, 0);
365 kDebug() <<
"failed to find knotify id for dbus_id" << dbus_id;
368 kDebug() <<
"action" << actKey <<
"invoked for notification " << id;
370 slotLinkClicked( QString(
"%1/%2").arg(
id).arg(actKey) );
373 closeNotificationDBus(
id);
376 void NotifyByPopup::slotDBusNotificationClosed(uint dbus_id, uint reason)
380 int
id = m_idMap.key(dbus_id, 0);
381 kDebug() << dbus_id << " -> " <<
id;
383 kDebug() <<
"failed to find knotify id for dbus_id" << dbus_id;
391 void NotifyByPopup::getAppCaptionAndIconName(
KNotifyConfig *config, QString *appCaption, QString *iconName)
393 KConfigGroup globalgroup(&(*config->
eventsfile),
"Global");
394 *appCaption = globalgroup.readEntry(
"Name", globalgroup.readEntry(
"Comment", config->
appname));
396 KConfigGroup eventGroup(&(*config->
eventsfile), QString(
"Event/%1").arg(config->
eventid));
397 if (eventGroup.hasKey(
"IconName")) {
398 *iconName = eventGroup.readEntry(
"IconName", config->
appname);
400 *iconName = globalgroup.readEntry(
"IconName", config->
appname);
404 bool NotifyByPopup::sendNotificationDBus(
int id,
int replacesId,
KNotifyConfig* config_nocheck)
407 uint dbus_replaces_id = 0;
408 if (replacesId != 0 ) {
409 dbus_replaces_id = m_idMap.value(replacesId, 0);
410 if (!dbus_replaces_id)
416 QList<QVariant> args;
418 QString appCaption, iconName;
419 getAppCaptionAndIconName(config_nocheck, &appCaption, &iconName);
421 KNotifyConfig *config = ensurePopupCompatibility( config_nocheck );
423 args.append( appCaption );
424 args.append( dbus_replaces_id );
425 args.append( iconName );
426 args.append( config->
title.isEmpty() ? appCaption : config->
title );
427 args.append( config->
text );
433 QStringList actionList;
435 foreach (
const QString& actName, config->
actions) {
437 actionList.append(QString::number(actId));
438 actionList.append(actName);
441 args.append( actionList );
447 if (!config->
appname.isEmpty()) {
448 map[
"x-kde-appname"] = config->
appname;
458 args.append( config->
timeout );
460 m.setArguments( args );
461 QDBusMessage replyMsg = QDBusConnection::sessionBus().call(m);
465 if(replyMsg.type() == QDBusMessage::ReplyMessage) {
466 if (!replyMsg.arguments().isEmpty()) {
467 uint dbus_id = replyMsg.arguments().at(0).toUInt();
470 kDebug() <<
"error: dbus_id is null";
473 if (dbus_replaces_id && dbus_id == dbus_replaces_id)
476 int oldId = m_idMap.key(dbus_id, 0);
478 kWarning() <<
"Received twice the same id "<< dbus_id <<
"( previous notification: " << oldId <<
")";
479 m_idMap.remove(oldId);
483 m_idMap.insert(
id, dbus_id);
484 kDebug() <<
"mapping knotify id to dbus id:"<<
id <<
"=>" << dbus_id;
488 kDebug() <<
"error: received reply with no arguments";
490 }
else if (replyMsg.type() == QDBusMessage::ErrorMessage) {
491 kDebug() <<
"error: failed to send dbus message";
493 kDebug() <<
"unexpected reply type";
498 void NotifyByPopup::closeNotificationDBus(
int id)
500 uint dbus_id = m_idMap.take(
id);
502 kDebug() <<
"not found dbus id to close" << id;
508 QList<QVariant> args;
509 args.append( dbus_id );
510 m.setArguments( args );
511 bool queued = QDBusConnection::sessionBus().send(m);
514 kDebug() <<
"warning: failed to queue dbus message";
521 if (!m_dbusServiceExists) {
526 return QStringList() <<
"actions" <<
"body" <<
"body-hyperlinks"
527 <<
"body-markup" <<
"icon-static";
531 if(m_dbusServiceCapCacheDirty) {
534 QDBusMessage replyMsg = QDBusConnection::sessionBus().call(m);
535 if (replyMsg.type() != QDBusMessage::ReplyMessage) {
536 kWarning(300) <<
"Error while calling popup server GetCapabilities()";
537 return QStringList();
540 if (replyMsg.arguments().isEmpty()) {
541 kWarning(300) <<
"popup server GetCapabilities() returned an empty reply";
542 return QStringList();
545 m_dbusServiceCapabilities = replyMsg.arguments().at(0).toStringList();
546 m_dbusServiceCapCacheDirty =
false;
549 return m_dbusServiceCapabilities;
558 if( !cap.contains(
"actions" ) )
563 if( !cap.contains(
"body-markup" ) )
565 if( c->
title.startsWith(
"<html>" ) )
567 if( c->
text.startsWith(
"<html>" ) )
568 c->
text = stripHtml( config->
text );
574 QString NotifyByPopup::stripHtml(
const QString &text )
576 QXmlStreamReader r(
"<elem>" + text +
"</elem>" );
577 HtmlEntityResolver resolver;
578 r.setEntityResolver( &resolver );
580 while( !r.atEnd() ) {
582 if( r.tokenType() == QXmlStreamReader::Characters )
584 result.append( r.text() );
586 else if( r.tokenType() == QXmlStreamReader::StartElement
587 && r.name() ==
"br" )
589 result.append(
"\n" );
596 kWarning(300) <<
"Notification to send to backend which does "
597 "not support HTML, contains invalid XML:"
598 << r.errorString() <<
"line" << r.lineNumber()
599 <<
"col" << r.columnNumber();
606 QString NotifyByPopup::HtmlEntityResolver::resolveUndeclaredEntity(
607 const QString &name )
610 QXmlStreamEntityResolver::resolveUndeclaredEntity(name);
611 if( !result.isEmpty() )
614 QChar ent = KCharsets::fromEntity(
'&' + name );
616 kWarning(300) <<
"Notification to send to backend which does "
617 "not support HTML, contains invalid entity: "
624 #include "notifybypopup.moc"
KSharedConfig::Ptr eventsfile
abstract class for KNotify actions
int timeout
How long the notification should be presented (in seconds).
QString text
the text of the notification
KNotifyConfig * copy() const
QString title
the title of the notification
QString eventid
the name of the notification
QStringList actions
the user-readable list of action.
void finished(int id)
the presentation is finished.
void finish(int id)
emit the finished signal you MUST call this function for each call to notify(), even if you do nothin...
WId winId
The windowsID of the window that initiated the notification (it is a window in the client) ...
KNotifyImage image
the pixmap to put on the notification
QVariant variantForImage(const QImage &_image)
Returns a variant representing an image using the format describe in the galago spec.
void actionInvoked(int id, int action)
emit this signal if one action was invoked
QString appname
the name of the application that triggered the notification
Represent the configuration for an event.