1/*
2 Copyright (C) 2005-2009 by Olivier Goffart <ogoffart at kde.org>
3
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 */
20
21#include "knotify.h"
22
23// KDE headers
24#include <kapplication.h>
25#include <kconfig.h>
26#include <kdebug.h>
27#include <kglobal.h>
28#include <klocale.h>
29#include <kservicetypetrader.h>
30
31#include <config-runtime.h>
32
33#include "knotifyconfig.h"
34#include "ksolidnotify.h"
35#include "notifybysound.h"
36#include "notifybypopup.h"
37#include "notifybyexecute.h"
38#include "notifybylogfile.h"
39#include "notifybytaskbar.h"
40#include "notifybyktts.h"
41
42
43
44KNotify::KNotify( QObject *parent )
45 : QObject( parent ),
46 m_counter(0)
47{
48 loadConfig();
49 (void)new KNotifyAdaptor(this);
50 (void)new KSolidNotify(this);
51 QDBusConnection::sessionBus().registerObject("/Notify", this, QDBusConnection::ExportAdaptors
52 /*| QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportScriptableSignals*/ );
53}
54
55KNotify::~KNotify()
56{
57 qDeleteAll(m_notifications);
58}
59
60
61void KNotify::loadConfig()
62{
63 qDeleteAll(m_plugins);
64 m_plugins.clear();
65 addPlugin(new NotifyBySound(this));
66 addPlugin(new NotifyByPopup(this));
67 addPlugin(new NotifyByExecute(this));
68 addPlugin(new NotifyByLogfile(this));
69 //TODO reactivate on Mac/Win when KWindowSystem::demandAttention will implemented on this system.
70#ifndef Q_WS_MAC
71 addPlugin(new NotifyByTaskbar(this));
72#endif
73 addPlugin(new NotifyByKTTS(this));
74
75 KService::List offers = KServiceTypeTrader::self()->query("KNotify/NotifyMethod");
76
77 QVariantList args;
78 QString error;
79
80 foreach (const KService::Ptr service, offers)
81 {
82 KNotifyPlugin *plugin = service->createInstance<KNotifyPlugin>(this, args, &error);
83 if (plugin)
84 {
85 addPlugin(plugin);
86 }
87 else
88 {
89 kDebug() << "Could not load plugin" << service->name() << "due to:" << error;
90 }
91 }
92}
93
94void KNotify::addPlugin( KNotifyPlugin * p )
95{
96 m_plugins[p->optionName()]=p;
97 connect(p,SIGNAL(finished( int )) , this , SLOT(slotPluginFinished( int ) ));
98 connect(p,SIGNAL(actionInvoked( int , int )) , this , SIGNAL(notificationActivated( int , int ) ));
99}
100
101
102
103void KNotify::reconfigure()
104{
105 KGlobal::config()->reparseConfiguration();
106 KNotifyConfig::reparseConfiguration();
107 loadConfig();
108}
109
110void KNotify::closeNotification(int id)
111{
112 if(!m_notifications.contains(id))
113 return;
114 Event *e=m_notifications[id];
115
116 kDebug() << e->id << " ref=" << e->ref;
117
118 //this has to be called before plugin->close or we will get double deletion because of slotPluginFinished
119 m_notifications.remove(id);
120
121 if(e->ref>0)
122 {
123 e->ref++;
124 KNotifyPlugin *plugin;
125 foreach(plugin , m_plugins)
126 {
127 plugin->close( id );
128 }
129 }
130 notificationClosed(id);
131 delete e;
132}
133
134int KNotify::event( const QString & event, const QString & appname, const ContextList & contexts, const QString & title, const QString & text, const KNotifyImage & image, const QStringList & actions, int timeout, WId winId )
135{
136 m_counter++;
137 Event *e=new Event(appname , contexts , event );
138 e->id = m_counter;
139 e->ref = 1;
140
141 e->config.title=title;
142 e->config.text=text;
143 e->config.actions=actions;
144 e->config.image=image;
145 e->config.timeout=timeout;
146 e->config.winId=(WId)winId;
147
148 m_notifications.insert(m_counter,e);
149 emitEvent(e);
150
151 e->ref--;
152 kDebug() << e->id << " ref=" << e->ref;
153 if(e->ref==0)
154 {
155 m_notifications.remove(e->id);
156 delete e;
157 return 0;
158 }
159 return m_counter;
160}
161
162void KNotify::update(int id, const QString &title, const QString &text, const KNotifyImage& image, const QStringList& actions)
163{
164 if(!m_notifications.contains(id))
165 return;
166
167 Event *e=m_notifications[id];
168
169 e->config.title=title;
170 e->config.text=text;
171 e->config.image = image;
172 e->config.actions = actions;
173
174 foreach(KNotifyPlugin *p, m_plugins)
175 {
176 p->update(id, &e->config);
177 }
178}
179void KNotify::reemit(int id, const ContextList& contexts)
180{
181 if(!m_notifications.contains(id))
182 return;
183 Event *e=m_notifications[id];
184 e->config.contexts=contexts;
185
186 emitEvent(e);
187}
188
189void KNotify::emitEvent(Event *e)
190{
191 QString presentstring=e->config.readEntry("Action");
192 QStringList presents=presentstring.split ('|');
193
194 if (!e->config.contexts.isEmpty() && !presents.first().isEmpty())
195 {
196 //Check whether the present actions are absolute, relative or invalid
197 bool relative = presents.first().startsWith('+') || presents.first().startsWith('-');
198 bool valid = true;
199 foreach (const QString & presentAction, presents)
200 valid &= ((presentAction.startsWith('+') || presentAction.startsWith('-')) == relative);
201 if (!valid)
202 {
203 kDebug() << "Context " << e->config.contexts << "present actions are invalid! Fallback to default present actions";
204 Event defaultEvent = Event(e->config.appname, ContextList(), e->config.eventid);
205 QString defaultPresentstring=defaultEvent.config.readEntry("Action");
206 presents = defaultPresentstring.split ('|');
207 } else if (relative)
208 {
209 // Obtain the list of present actions without context
210 Event noContextEvent = Event(e->config.appname, ContextList(), e->config.eventid);
211 QString noContextPresentstring = noContextEvent.config.readEntry("Action");
212 QSet<QString> noContextPresents = noContextPresentstring.split ('|').toSet();
213 foreach (const QString & presentAction, presents)
214 {
215 if (presentAction.startsWith('+'))
216 noContextPresents << presentAction.mid(1);
217 else
218 noContextPresents.remove(presentAction.mid(1));
219 }
220 presents = noContextPresents.toList();
221 }
222 }
223
224 foreach(const QString & action , presents)
225 {
226 if(!m_plugins.contains(action))
227 continue;
228 KNotifyPlugin *p=m_plugins[action];
229 e->ref++;
230 p->notify(e->id,&e->config);
231 }
232}
233
234void KNotify::slotPluginFinished( int id )
235{
236 if(!m_notifications.contains(id))
237 return;
238 Event *e=m_notifications[id];
239 kDebug() << e->id << " ref=" << e->ref ;
240 e->ref--;
241 if(e->ref==0)
242 closeNotification( id );
243}
244
245KNotifyAdaptor::KNotifyAdaptor(QObject *parent)
246 : QDBusAbstractAdaptor(parent)
247{
248 setAutoRelaySignals(true);
249}
250
251void KNotifyAdaptor::reconfigure()
252{
253 static_cast<KNotify *>(parent())->reconfigure();
254}
255
256void KNotifyAdaptor::closeNotification(int id)
257{
258 static_cast<KNotify *>(parent())->closeNotification(id);
259}
260
261int KNotifyAdaptor::event(const QString &event, const QString &fromApp, const QVariantList& contexts,
262 const QString &title, const QString &text, const QByteArray& image, const QStringList& actions,
263 int timeout, qlonglong winId)
264// const QDBusMessage & , int _return )
265
266{
267 /* I'm not sure this is the right way to read a a(ss) type, but it seems to work */
268 ContextList contextlist;
269 QString context_key;
270 foreach( const QVariant &v , contexts)
271 {
272 /* this code doesn't work
273 QVariantList vl=v.toList();
274 if(vl.count() != 2)
275 {
276 kWarning() << "Bad structure passed as argument" ;
277 continue;
278 }
279 contextlist << qMakePair(vl[0].toString() , vl[1].toString());*/
280 QString s=v.toString();
281 if(context_key.isEmpty())
282 context_key=s;
283 else {
284 contextlist << qMakePair(context_key , s);
285 context_key = "";
286 }
287 }
288
289 return static_cast<KNotify *>(parent())->event(event, fromApp, contextlist, title, text, image, actions, timeout, WId(winId));
290}
291
292void KNotifyAdaptor::reemit(int id, const QVariantList& contexts)
293{
294 ContextList contextlist;
295 QString context_key;
296 foreach( const QVariant &v , contexts)
297 {
298 QString s=v.toString();
299 if(context_key.isEmpty())
300 context_key=s;
301 else {
302 contextlist << qMakePair(context_key , s);
303 context_key = "";
304 }
305 }
306 static_cast<KNotify *>(parent())->reemit(id, contextlist);
307}
308
309
310void KNotifyAdaptor::update(int id, const QString &title, const QString &text, const QByteArray& image, const QStringList& actions )
311{
312 static_cast<KNotify *>(parent())->update(id, title, text, image, actions);
313}
314
315#include "knotify.moc"
316
317// vim: sw=4 sts=4 ts=8 et
318
319
320