1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 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 | Icon 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::() 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_KDE |
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 |
368 | |