1 /***************************************************************************
2 * Copyright (C) 2005-2015 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This contains code from KStatusNotifierItem, part of the KDE libs *
6 * Copyright (C) 2009 Marco Martin <notmart@gmail.com> *
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. *
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. *
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 ***************************************************************************/
26 #include <QApplication>
28 #include <QMouseEvent>
29 #include <QTextDocument>
32 #include "statusnotifieritem.h"
33 #include "statusnotifieritemdbus.h"
35 const int StatusNotifierItem::_protocolVersion = 0;
36 const QString StatusNotifierItem::_statusNotifierWatcherServiceName("org.kde.StatusNotifierWatcher");
39 # include "dbusmenuexporter.h"
42 * Specialization to provide access to icon names
44 class QuasselDBusMenuExporter : public DBusMenuExporter
47 QuasselDBusMenuExporter(const QString &dbusObjectPath, QMenu *menu, const QDBusConnection &dbusConnection)
48 : DBusMenuExporter(dbusObjectPath, menu, dbusConnection)
52 virtual QString iconNameForAction(QAction *action) // TODO Qt 4.7: fixme when we have converted our iconloader
54 QIcon icon(action->icon());
55 return icon.isNull() ? QString() : icon.name();
60 #endif /* HAVE_DBUSMENU */
62 StatusNotifierItem::StatusNotifierItem(QWidget *parent)
63 : StatusNotifierItemParent(parent),
64 _statusNotifierItemDBus(0),
65 _statusNotifierWatcher(0),
66 _notificationsClient(0),
67 _notificationsClientSupportsMarkup(true),
68 _lastNotificationsDBusId(0)
73 StatusNotifierItem::~StatusNotifierItem()
75 delete _statusNotifierWatcher;
79 void StatusNotifierItem::init()
81 qDBusRegisterMetaType<DBusImageStruct>();
82 qDBusRegisterMetaType<DBusImageVector>();
83 qDBusRegisterMetaType<DBusToolTipStruct>();
85 _statusNotifierItemDBus = new StatusNotifierItemDBus(this);
87 connect(this, SIGNAL(toolTipChanged(QString, QString)), _statusNotifierItemDBus, SIGNAL(NewToolTip()));
88 connect(this, SIGNAL(animationEnabledChanged(bool)), _statusNotifierItemDBus, SIGNAL(NewAttentionIcon()));
90 QDBusServiceWatcher *watcher = new QDBusServiceWatcher(_statusNotifierWatcherServiceName,
91 QDBusConnection::sessionBus(),
92 QDBusServiceWatcher::WatchForOwnerChange,
94 connect(watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), SLOT(serviceChange(QString, QString, QString)));
96 setMode(StatusNotifier);
98 _notificationsClient = new org::freedesktop::Notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications",
99 QDBusConnection::sessionBus(), this);
101 connect(_notificationsClient, SIGNAL(NotificationClosed(uint, uint)), SLOT(notificationClosed(uint, uint)));
102 connect(_notificationsClient, SIGNAL(ActionInvoked(uint, QString)), SLOT(notificationInvoked(uint, QString)));
104 if (_notificationsClient->isValid()) {
105 QStringList desktopCapabilities = _notificationsClient->GetCapabilities();
106 _notificationsClientSupportsMarkup = desktopCapabilities.contains("body-markup");
107 _notificationsClientSupportsActions = desktopCapabilities.contains("actions");
110 StatusNotifierItemParent::init();
111 trayMenu()->installEventFilter(this);
113 // use the appdata icon folder for now
114 _iconThemePath = Quassel::findDataFilePath("icons");
117 _menuObjectPath = "/MenuBar";
118 new QuasselDBusMenuExporter(menuObjectPath(), trayMenu(), _statusNotifierItemDBus->dbusConnection()); // will be added as menu child
123 void StatusNotifierItem::registerToDaemon()
125 if (!_statusNotifierWatcher) {
126 _statusNotifierWatcher = new org::kde::StatusNotifierWatcher(_statusNotifierWatcherServiceName,
127 "/StatusNotifierWatcher",
128 QDBusConnection::sessionBus());
129 connect(_statusNotifierWatcher, SIGNAL(StatusNotifierHostRegistered()), SLOT(checkForRegisteredHosts()));
130 connect(_statusNotifierWatcher, SIGNAL(StatusNotifierHostUnregistered()), SLOT(checkForRegisteredHosts()));
132 if (_statusNotifierWatcher->isValid()
133 && _statusNotifierWatcher->property("ProtocolVersion").toInt() == _protocolVersion) {
134 _statusNotifierWatcher->RegisterStatusNotifierItem(_statusNotifierItemDBus->service());
135 checkForRegisteredHosts();
138 //qDebug() << "StatusNotifierWatcher not reachable!";
144 void StatusNotifierItem::serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner)
147 if (newOwner.isEmpty()) {
149 //qDebug() << "Connection to the StatusNotifierWatcher lost";
150 delete _statusNotifierWatcher;
151 _statusNotifierWatcher = 0;
154 else if (oldOwner.isEmpty()) {
156 setMode(StatusNotifier);
161 void StatusNotifierItem::checkForRegisteredHosts()
163 if (!_statusNotifierWatcher || !_statusNotifierWatcher->property("IsStatusNotifierHostRegistered").toBool())
166 setMode(StatusNotifier);
170 bool StatusNotifierItem::isSystemTrayAvailable() const
172 if (mode() == StatusNotifier)
173 return true; // else it should be set to legacy on registration
175 return StatusNotifierItemParent::isSystemTrayAvailable();
179 bool StatusNotifierItem::isVisible() const
181 if (mode() == StatusNotifier)
182 return shouldBeVisible(); // we don't have a way to check, so we need to trust everything went right
184 return StatusNotifierItemParent::isVisible();
188 void StatusNotifierItem::setMode(Mode mode_)
193 if (mode_ != StatusNotifier) {
194 _statusNotifierItemDBus->unregisterService();
197 StatusNotifierItemParent::setMode(mode_);
199 if (mode() == StatusNotifier) {
200 _statusNotifierItemDBus->registerService();
206 void StatusNotifierItem::setState(State state_)
208 StatusNotifierItemParent::setState(state_);
210 emit _statusNotifierItemDBus->NewStatus(metaObject()->enumerator(metaObject()->indexOfEnumerator("State")).valueToKey(state()));
211 emit _statusNotifierItemDBus->NewIcon();
215 void StatusNotifierItem::setVisible(bool visible)
217 if (visible == isVisible())
220 LegacySystemTray::setVisible(visible);
222 if (mode() == StatusNotifier) {
223 if (shouldBeVisible()) {
224 _statusNotifierItemDBus->registerService();
228 _statusNotifierItemDBus->unregisterService();
229 _statusNotifierWatcher->deleteLater();
230 _statusNotifierWatcher = 0;
236 QString StatusNotifierItem::title() const
238 return QString("Quassel IRC");
242 QString StatusNotifierItem::iconName() const
244 if (state() == Passive)
245 return QString("quassel-inactive");
247 return QString("quassel");
251 QString StatusNotifierItem::attentionIconName() const
253 if (animationEnabled())
254 return QString("quassel-message");
256 return QString("quassel");
260 QString StatusNotifierItem::toolTipIconName() const
262 return QString("quassel");
266 QString StatusNotifierItem::iconThemePath() const
268 return _iconThemePath;
272 QString StatusNotifierItem::menuObjectPath() const
274 return _menuObjectPath;
278 void StatusNotifierItem::activated(const QPoint &pos)
285 bool StatusNotifierItem::eventFilter(QObject *watched, QEvent *event)
287 if (mode() == StatusNotifier) {
288 //FIXME: ugly ugly workaround to weird QMenu's focus problems
290 if (watched == trayMenu() &&
291 (event->type() == QEvent::WindowDeactivate || (event->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton))) {
292 // put at the back of event queue to let the action activate anyways
293 QTimer::singleShot(0, trayMenu(), SLOT(hide()));
296 if (watched == trayMenu() && event->type() == QEvent::HoverLeave) {
301 return StatusNotifierItemParent::eventFilter(watched, event);
305 void StatusNotifierItem::showMessage(const QString &title, const QString &message_, SystemTray::MessageIcon icon, int timeout, uint notificationId)
307 QString message = message_;
308 if (_notificationsClient->isValid()) {
309 if (_notificationsClientSupportsMarkup)
310 #if QT_VERSION < 0x050000
311 message = Qt::escape(message);
313 message = message.toHtmlEscaped();
317 if (_notificationsClientSupportsActions)
318 actions << "activate" << "View";
320 // we always queue notifications right now
321 QDBusReply<uint> reply = _notificationsClient->Notify(title, 0, "quassel", title, message, actions, QVariantMap(), timeout);
322 if (reply.isValid()) {
323 uint dbusid = reply.value();
324 _notificationsIdMap.insert(dbusid, notificationId);
325 _lastNotificationsDBusId = dbusid;
329 StatusNotifierItemParent::showMessage(title, message, icon, timeout, notificationId);
333 void StatusNotifierItem::closeMessage(uint notificationId)
335 foreach(uint dbusid, _notificationsIdMap.keys()) {
336 if (_notificationsIdMap.value(dbusid) == notificationId) {
337 _notificationsIdMap.remove(dbusid);
338 _notificationsClient->CloseNotification(dbusid);
341 _lastNotificationsDBusId = 0;
345 void StatusNotifierItem::notificationClosed(uint dbusid, uint reason)
348 _lastNotificationsDBusId = 0;
349 emit messageClosed(_notificationsIdMap.take(dbusid));
353 void StatusNotifierItem::notificationInvoked(uint dbusid, const QString &action)
356 emit messageClicked(_notificationsIdMap.value(dbusid, 0));