Merge pull request #105 from merlin1991/qdatastream
[quassel.git] / src / qtui / statusnotifieritem.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2015 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This contains code from KStatusNotifierItem, part of the KDE libs     *
6  *   Copyright (C) 2009 Marco Martin <notmart@gmail.com>                   *
7  *                                                                         *
8  *   This file is free software; you can redistribute it and/or modify     *
9  *   it under the terms of the GNU Library General Public License (LGPL)   *
10  *   as published by the Free Software Foundation; either version 2 of the *
11  *   License, or (at your option) any later version.                       *
12  *                                                                         *
13  *   This program is distributed in the hope that it will be useful,       *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
16  *   GNU General Public License for more details.                          *
17  *                                                                         *
18  *   You should have received a copy of the GNU General Public License     *
19  *   along with this program; if not, write to the                         *
20  *   Free Software Foundation, Inc.,                                       *
21  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
22  ***************************************************************************/
23
24 #ifdef HAVE_DBUS
25
26 #include <QApplication>
27 #include <QMenu>
28 #include <QMouseEvent>
29 #include <QTextDocument>
30
31 #include "quassel.h"
32 #include "statusnotifieritem.h"
33 #include "statusnotifieritemdbus.h"
34
35 const int StatusNotifierItem::_protocolVersion = 0;
36 const QString StatusNotifierItem::_statusNotifierWatcherServiceName("org.kde.StatusNotifierWatcher");
37
38 #ifdef HAVE_DBUSMENU
39 #  include "dbusmenuexporter.h"
40
41 /**
42  * Specialization to provide access to icon names
43  */
44 class QuasselDBusMenuExporter : public DBusMenuExporter
45 {
46 public:
47     QuasselDBusMenuExporter(const QString &dbusObjectPath, QMenu *menu, const QDBusConnection &dbusConnection)
48         : DBusMenuExporter(dbusObjectPath, menu, dbusConnection)
49     {}
50
51 protected:
52     virtual QString iconNameForAction(QAction *action) // TODO Qt 4.7: fixme when we have converted our iconloader
53     {
54         QIcon icon(action->icon());
55 #if QT_VERSION >= 0x040701
56         // QIcon::name() is in the 4.7 git branch, but it is not in 4.7 TP.
57         // If you get a build error here, you need to update your pre-release
58         // of Qt 4.7.
59         return icon.isNull() ? QString() : icon.name();
60 #else
61         return QString();
62 #endif
63     }
64 };
65
66
67 #endif /* HAVE_DBUSMENU */
68
69 StatusNotifierItem::StatusNotifierItem(QWidget *parent)
70     : StatusNotifierItemParent(parent),
71     _statusNotifierItemDBus(0),
72     _statusNotifierWatcher(0),
73     _notificationsClient(0),
74     _notificationsClientSupportsMarkup(true),
75     _lastNotificationsDBusId(0)
76 {
77 }
78
79
80 StatusNotifierItem::~StatusNotifierItem()
81 {
82     delete _statusNotifierWatcher;
83 }
84
85
86 void StatusNotifierItem::init()
87 {
88     qDBusRegisterMetaType<DBusImageStruct>();
89     qDBusRegisterMetaType<DBusImageVector>();
90     qDBusRegisterMetaType<DBusToolTipStruct>();
91
92     _statusNotifierItemDBus = new StatusNotifierItemDBus(this);
93
94     connect(this, SIGNAL(toolTipChanged(QString, QString)), _statusNotifierItemDBus, SIGNAL(NewToolTip()));
95     connect(this, SIGNAL(animationEnabledChanged(bool)), _statusNotifierItemDBus, SIGNAL(NewAttentionIcon()));
96
97     QDBusServiceWatcher *watcher = new QDBusServiceWatcher(_statusNotifierWatcherServiceName,
98         QDBusConnection::sessionBus(),
99         QDBusServiceWatcher::WatchForOwnerChange,
100         this);
101     connect(watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), SLOT(serviceChange(QString, QString, QString)));
102
103     setMode(StatusNotifier);
104
105     _notificationsClient = new org::freedesktop::Notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications",
106         QDBusConnection::sessionBus(), this);
107
108     connect(_notificationsClient, SIGNAL(NotificationClosed(uint, uint)), SLOT(notificationClosed(uint, uint)));
109     connect(_notificationsClient, SIGNAL(ActionInvoked(uint, QString)), SLOT(notificationInvoked(uint, QString)));
110
111     if (_notificationsClient->isValid()) {
112         QStringList desktopCapabilities = _notificationsClient->GetCapabilities();
113         _notificationsClientSupportsMarkup = desktopCapabilities.contains("body-markup");
114         _notificationsClientSupportsActions = desktopCapabilities.contains("actions");
115     }
116
117     StatusNotifierItemParent::init();
118     trayMenu()->installEventFilter(this);
119
120     // use the appdata icon folder for now
121     _iconThemePath = Quassel::findDataFilePath("icons");
122
123 #ifdef HAVE_DBUSMENU
124     _menuObjectPath = "/MenuBar";
125     new QuasselDBusMenuExporter(menuObjectPath(), trayMenu(), _statusNotifierItemDBus->dbusConnection()); // will be added as menu child
126 #endif
127 }
128
129
130 void StatusNotifierItem::registerToDaemon()
131 {
132     if (!_statusNotifierWatcher) {
133         _statusNotifierWatcher = new org::kde::StatusNotifierWatcher(_statusNotifierWatcherServiceName,
134             "/StatusNotifierWatcher",
135             QDBusConnection::sessionBus());
136         connect(_statusNotifierWatcher, SIGNAL(StatusNotifierHostRegistered()), SLOT(checkForRegisteredHosts()));
137         connect(_statusNotifierWatcher, SIGNAL(StatusNotifierHostUnregistered()), SLOT(checkForRegisteredHosts()));
138     }
139     if (_statusNotifierWatcher->isValid()
140         && _statusNotifierWatcher->property("ProtocolVersion").toInt() == _protocolVersion) {
141         _statusNotifierWatcher->RegisterStatusNotifierItem(_statusNotifierItemDBus->service());
142         checkForRegisteredHosts();
143     }
144     else {
145         //qDebug() << "StatusNotifierWatcher not reachable!";
146         setMode(Legacy);
147     }
148 }
149
150
151 void StatusNotifierItem::serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner)
152 {
153     Q_UNUSED(name);
154     if (newOwner.isEmpty()) {
155         //unregistered
156         //qDebug() << "Connection to the StatusNotifierWatcher lost";
157         delete _statusNotifierWatcher;
158         _statusNotifierWatcher = 0;
159         setMode(Legacy);
160     }
161     else if (oldOwner.isEmpty()) {
162         //registered
163         setMode(StatusNotifier);
164     }
165 }
166
167
168 void StatusNotifierItem::checkForRegisteredHosts()
169 {
170     if (!_statusNotifierWatcher || !_statusNotifierWatcher->property("IsStatusNotifierHostRegistered").toBool())
171         setMode(Legacy);
172     else
173         setMode(StatusNotifier);
174 }
175
176
177 bool StatusNotifierItem::isSystemTrayAvailable() const
178 {
179     if (mode() == StatusNotifier)
180         return true;  // else it should be set to legacy on registration
181
182     return StatusNotifierItemParent::isSystemTrayAvailable();
183 }
184
185
186 bool StatusNotifierItem::isVisible() const
187 {
188     if (mode() == StatusNotifier)
189         return shouldBeVisible();  // we don't have a way to check, so we need to trust everything went right
190
191     return StatusNotifierItemParent::isVisible();
192 }
193
194
195 void StatusNotifierItem::setMode(Mode mode_)
196 {
197     if (mode_ == mode())
198         return;
199
200     if (mode_ != StatusNotifier) {
201         _statusNotifierItemDBus->unregisterService();
202     }
203
204     StatusNotifierItemParent::setMode(mode_);
205
206     if (mode() == StatusNotifier) {
207         _statusNotifierItemDBus->registerService();
208         registerToDaemon();
209     }
210 }
211
212
213 void StatusNotifierItem::setState(State state_)
214 {
215     StatusNotifierItemParent::setState(state_);
216
217     emit _statusNotifierItemDBus->NewStatus(metaObject()->enumerator(metaObject()->indexOfEnumerator("State")).valueToKey(state()));
218     emit _statusNotifierItemDBus->NewIcon();
219 }
220
221
222 void StatusNotifierItem::setVisible(bool visible)
223 {
224     if (visible == isVisible())
225         return;
226
227     LegacySystemTray::setVisible(visible);
228
229     if (mode() == StatusNotifier) {
230         if (shouldBeVisible()) {
231             _statusNotifierItemDBus->registerService();
232             registerToDaemon();
233         }
234         else {
235             _statusNotifierItemDBus->unregisterService();
236             _statusNotifierWatcher->deleteLater();
237             _statusNotifierWatcher = 0;
238         }
239     }
240 }
241
242
243 QString StatusNotifierItem::title() const
244 {
245     return QString("Quassel IRC");
246 }
247
248
249 QString StatusNotifierItem::iconName() const
250 {
251     if (state() == Passive)
252         return QString("quassel-inactive");
253     else
254         return QString("quassel");
255 }
256
257
258 QString StatusNotifierItem::attentionIconName() const
259 {
260     if (animationEnabled())
261         return QString("quassel-message");
262     else
263         return QString("quassel");
264 }
265
266
267 QString StatusNotifierItem::toolTipIconName() const
268 {
269     return QString("quassel");
270 }
271
272
273 QString StatusNotifierItem::iconThemePath() const
274 {
275     return _iconThemePath;
276 }
277
278
279 QString StatusNotifierItem::menuObjectPath() const
280 {
281     return _menuObjectPath;
282 }
283
284
285 void StatusNotifierItem::activated(const QPoint &pos)
286 {
287     Q_UNUSED(pos)
288     activate(Trigger);
289 }
290
291
292 bool StatusNotifierItem::eventFilter(QObject *watched, QEvent *event)
293 {
294     if (mode() == StatusNotifier) {
295         //FIXME: ugly ugly workaround to weird QMenu's focus problems
296 #ifdef HAVE_KDE4
297         if (watched == trayMenu() &&
298             (event->type() == QEvent::WindowDeactivate || (event->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton))) {
299             // put at the back of event queue to let the action activate anyways
300             QTimer::singleShot(0, trayMenu(), SLOT(hide()));
301         }
302 #else
303         if (watched == trayMenu() && event->type() == QEvent::HoverLeave) {
304             trayMenu()->hide();
305         }
306 #endif
307     }
308     return StatusNotifierItemParent::eventFilter(watched, event);
309 }
310
311
312 void StatusNotifierItem::showMessage(const QString &title, const QString &message_, SystemTray::MessageIcon icon, int timeout, uint notificationId)
313 {
314     QString message = message_;
315     if (_notificationsClient->isValid()) {
316         if (_notificationsClientSupportsMarkup)
317 #if QT_VERSION < 0x050000
318             message = Qt::escape(message);
319 #else
320             message = message.toHtmlEscaped();
321 #endif
322
323         QStringList actions;
324         if (_notificationsClientSupportsActions)
325             actions << "activate" << "View";
326
327         // we always queue notifications right now
328         QDBusReply<uint> reply = _notificationsClient->Notify(title, 0, "quassel", title, message, actions, QVariantMap(), timeout);
329         if (reply.isValid()) {
330             uint dbusid = reply.value();
331             _notificationsIdMap.insert(dbusid, notificationId);
332             _lastNotificationsDBusId = dbusid;
333         }
334     }
335     else
336         StatusNotifierItemParent::showMessage(title, message, icon, timeout, notificationId);
337 }
338
339
340 void StatusNotifierItem::closeMessage(uint notificationId)
341 {
342     foreach(uint dbusid, _notificationsIdMap.keys()) {
343         if (_notificationsIdMap.value(dbusid) == notificationId) {
344             _notificationsIdMap.remove(dbusid);
345             _notificationsClient->CloseNotification(dbusid);
346         }
347     }
348     _lastNotificationsDBusId = 0;
349 }
350
351
352 void StatusNotifierItem::notificationClosed(uint dbusid, uint reason)
353 {
354     Q_UNUSED(reason)
355     _lastNotificationsDBusId = 0;
356     emit messageClosed(_notificationsIdMap.take(dbusid));
357 }
358
359
360 void StatusNotifierItem::notificationInvoked(uint dbusid, const QString &action)
361 {
362     Q_UNUSED(action)
363     emit messageClicked(_notificationsIdMap.value(dbusid, 0));
364 }
365
366
367 #endif