X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=src%2Fqtui%2Fstatusnotifieritem.cpp;h=e96cd4eb7f148147aebd762c9feb06796f816142;hp=da15475b84bc351c66cf3c79e8cd87931a29203d;hb=f04db2cb802b1296ca739c823495930c71d3b4ab;hpb=b858144c9d38623bdd9afaa02c404d9515243ab7 diff --git a/src/qtui/statusnotifieritem.cpp b/src/qtui/statusnotifieritem.cpp index da15475b..e96cd4eb 100644 --- a/src/qtui/statusnotifieritem.cpp +++ b/src/qtui/statusnotifieritem.cpp @@ -1,14 +1,14 @@ /*************************************************************************** - * Copyright (C) 2005-2010 by the Quassel Project * + * Copyright (C) 2005-2013 by the Quassel Project * * devel@quassel-irc.org * * * * This contains code from KStatusNotifierItem, part of the KDE libs * * Copyright (C) 2009 Marco Martin * * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) version 3. * + * This file is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Library General Public License (LGPL) * + * as published by the Free Software Foundation; either version 2 of the * + * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * @@ -18,160 +18,346 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifdef HAVE_DBUS -#include "statusnotifieritem.h" -#include "statusnotifieritemdbus.h" - +#include #include #include +#include + +#include "quassel.h" +#include "statusnotifieritem.h" +#include "statusnotifieritemdbus.h" const int StatusNotifierItem::_protocolVersion = 0; +const QString StatusNotifierItem::_statusNotifierWatcherServiceName("org.kde.StatusNotifierWatcher"); -StatusNotifierItem::StatusNotifierItem(QWidget *parent) - : StatusNotifierItemParent(parent), - _statusNotifierItemDBus(0), - _statusNotifierWatcher(0) +#ifdef HAVE_DBUSMENU +# include "dbusmenuexporter.h" + +/** + * Specialization to provide access to icon names + */ +class QuasselDBusMenuExporter : public DBusMenuExporter { +public: + QuasselDBusMenuExporter(const QString &dbusObjectPath, QMenu *menu, const QDBusConnection &dbusConnection) + : DBusMenuExporter(dbusObjectPath, menu, dbusConnection) + {} + +protected: + virtual QString iconNameForAction(QAction *action) // TODO Qt 4.7: fixme when we have converted our iconloader + { + Icon icon(action->icon()); +#if QT_VERSION >= 0x040701 + // QIcon::name() is in the 4.7 git branch, but it is not in 4.7 TP. + // If you get a build error here, you need to update your pre-release + // of Qt 4.7. + return icon.isNull() ? QString() : icon.name(); +#else + return QString(); +#endif + } +}; + + +#endif /* HAVE_DBUSMENU */ +StatusNotifierItem::StatusNotifierItem(QWidget *parent) + : StatusNotifierItemParent(parent), + _statusNotifierItemDBus(0), + _statusNotifierWatcher(0), + _notificationsClient(0), + _notificationsClientSupportsMarkup(true), + _lastNotificationsDBusId(0) +{ } -StatusNotifierItem::~StatusNotifierItem() { - delete _statusNotifierWatcher; +StatusNotifierItem::~StatusNotifierItem() +{ + delete _statusNotifierWatcher; } -void StatusNotifierItem::init() { -// workaround until we handle the tray menu more sanely -#ifdef QT_NO_SYSTEMTRAYICON - setTrayMenu(new QMenu(associatedWidget())); + +void StatusNotifierItem::init() +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + _statusNotifierItemDBus = new StatusNotifierItemDBus(this); + + connect(this, SIGNAL(toolTipChanged(QString, QString)), _statusNotifierItemDBus, SIGNAL(NewToolTip())); + connect(this, SIGNAL(animationEnabledChanged(bool)), _statusNotifierItemDBus, SIGNAL(NewAttentionIcon())); + + QDBusServiceWatcher *watcher = new QDBusServiceWatcher(_statusNotifierWatcherServiceName, + QDBusConnection::sessionBus(), + QDBusServiceWatcher::WatchForOwnerChange, + this); + connect(watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), SLOT(serviceChange(QString, QString, QString))); + + setMode(StatusNotifier); + + _notificationsClient = new org::freedesktop::Notifications("org.freedesktop.Notifications", "/org/freedesktop/Notifications", + QDBusConnection::sessionBus(), this); + + connect(_notificationsClient, SIGNAL(NotificationClosed(uint, uint)), SLOT(notificationClosed(uint, uint))); + connect(_notificationsClient, SIGNAL(ActionInvoked(uint, QString)), SLOT(notificationInvoked(uint, QString))); + + if (_notificationsClient->isValid()) { + QStringList desktopCapabilities = _notificationsClient->GetCapabilities(); + _notificationsClientSupportsMarkup = desktopCapabilities.contains("body-markup"); + _notificationsClientSupportsActions = desktopCapabilities.contains("actions"); + } + + StatusNotifierItemParent::init(); + trayMenu()->installEventFilter(this); + + // use the appdata icon folder for now + _iconThemePath = Quassel::findDataFilePath("icons"); + +#ifdef HAVE_DBUSMENU + _menuObjectPath = "/MenuBar"; + new QuasselDBusMenuExporter(menuObjectPath(), trayMenu(), _statusNotifierItemDBus->dbusConnection()); // will be added as menu child #endif +} - trayMenu()->installEventFilter(this); - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); - qDBusRegisterMetaType(); +void StatusNotifierItem::registerToDaemon() +{ + if (!_statusNotifierWatcher) { + _statusNotifierWatcher = new org::kde::StatusNotifierWatcher(_statusNotifierWatcherServiceName, + "/StatusNotifierWatcher", + QDBusConnection::sessionBus()); + connect(_statusNotifierWatcher, SIGNAL(StatusNotifierHostRegistered()), SLOT(checkForRegisteredHosts())); + connect(_statusNotifierWatcher, SIGNAL(StatusNotifierHostUnregistered()), SLOT(checkForRegisteredHosts())); + } + if (_statusNotifierWatcher->isValid() + && _statusNotifierWatcher->property("ProtocolVersion").toInt() == _protocolVersion) { + _statusNotifierWatcher->RegisterStatusNotifierItem(_statusNotifierItemDBus->service()); + checkForRegisteredHosts(); + } + else { + //qDebug() << "StatusNotifierWatcher not reachable!"; + setMode(Legacy); + } +} - _statusNotifierItemDBus = new StatusNotifierItemDBus(this); - connect(QDBusConnection::sessionBus().interface(), SIGNAL(serviceOwnerChanged(QString,QString,QString)), - SLOT(serviceChange(QString,QString,QString))); +void StatusNotifierItem::serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner) +{ + Q_UNUSED(name); + if (newOwner.isEmpty()) { + //unregistered + //qDebug() << "Connection to the StatusNotifierWatcher lost"; + delete _statusNotifierWatcher; + _statusNotifierWatcher = 0; + setMode(Legacy); + } + else if (oldOwner.isEmpty()) { + //registered + setMode(StatusNotifier); + } +} - setMode(StatusNotifier); - StatusNotifierItemParent::init(); +void StatusNotifierItem::checkForRegisteredHosts() +{ + if (!_statusNotifierWatcher || !_statusNotifierWatcher->property("IsStatusNotifierHostRegistered").toBool()) + setMode(Legacy); + else + setMode(StatusNotifier); } -void StatusNotifierItem::registerToDaemon() { - if(!_statusNotifierWatcher) { - QString interface("org.kde.StatusNotifierWatcher"); - _statusNotifierWatcher = new org::kde::StatusNotifierWatcher(interface, "/StatusNotifierWatcher", - QDBusConnection::sessionBus()); - } - if(_statusNotifierWatcher->isValid() - && _statusNotifierWatcher->property("ProtocolVersion").toInt() == _protocolVersion) { - _statusNotifierWatcher->RegisterStatusNotifierItem(_statusNotifierItemDBus->service()); +bool StatusNotifierItem::isSystemTrayAvailable() const +{ + if (mode() == StatusNotifier) + return true; // else it should be set to legacy on registration - } else { - qDebug() << "StatusNotifierWatcher not reachable!"; - setMode(Legacy); - } + return StatusNotifierItemParent::isSystemTrayAvailable(); } -// FIXME remove deprecated slot with Qt 4.6 -void StatusNotifierItem::serviceChange(const QString& name, const QString& oldOwner, const QString& newOwner) { - bool legacy = false; - if(name == "org.kde.StatusNotifierWatcher") { - if(newOwner.isEmpty()) { - //unregistered - //qDebug() << "Connection to the StatusNotifierWatcher lost"; - legacy = true; - } else if(oldOwner.isEmpty()) { - //registered - legacy = false; + +bool StatusNotifierItem::isVisible() const +{ + if (mode() == StatusNotifier) + return shouldBeVisible(); // we don't have a way to check, so we need to trust everything went right + + return StatusNotifierItemParent::isVisible(); +} + + +void StatusNotifierItem::setMode(Mode mode_) +{ + if (mode_ == mode()) + return; + + if (mode_ != StatusNotifier) { + _statusNotifierItemDBus->unregisterService(); } - } else if(name.startsWith(QLatin1String("org.kde.StatusNotifierHost-"))) { - if(newOwner.isEmpty() && (!_statusNotifierWatcher || - !_statusNotifierWatcher->property("IsStatusNotifierHostRegistered").toBool())) { - //qDebug() << "Connection to the last StatusNotifierHost lost"; - legacy = true; - } else if(oldOwner.isEmpty()) { - //qDebug() << "New StatusNotifierHost"; - legacy = false; + + StatusNotifierItemParent::setMode(mode_); + + if (mode() == StatusNotifier) { + _statusNotifierItemDBus->registerService(); + registerToDaemon(); } - } else { - return; - } +} - // qDebug() << "Service " << name << "status change, old owner:" << oldOwner << "new:" << newOwner; - if(legacy == (mode() == Legacy)) { - return; - } +void StatusNotifierItem::setState(State state_) +{ + StatusNotifierItemParent::setState(state_); - if(legacy) { - //unregistered - setMode(Legacy); - } else { - //registered - setMode(StatusNotifier); - } + emit _statusNotifierItemDBus->NewStatus(metaObject()->enumerator(metaObject()->indexOfEnumerator("State")).valueToKey(state())); + emit _statusNotifierItemDBus->NewIcon(); } -void StatusNotifierItem::setMode(Mode mode_) { - StatusNotifierItemParent::setMode(mode_); - if(mode() == StatusNotifier) { - registerToDaemon(); - } +void StatusNotifierItem::setVisible(bool visible) +{ + if (visible == isVisible()) + return; + + LegacySystemTray::setVisible(visible); + + if (mode() == StatusNotifier) { + if (shouldBeVisible()) { + _statusNotifierItemDBus->registerService(); + registerToDaemon(); + } + else { + _statusNotifierItemDBus->unregisterService(); + _statusNotifierWatcher->deleteLater(); + _statusNotifierWatcher = 0; + } + } } -void StatusNotifierItem::setState(State state_) { - StatusNotifierItemParent::setState(state_); - emit _statusNotifierItemDBus->NewStatus(metaObject()->enumerator(metaObject()->indexOfEnumerator("State")).valueToKey(state())); - emit _statusNotifierItemDBus->NewIcon(); +QString StatusNotifierItem::title() const +{ + return QString("Quassel IRC"); } -QString StatusNotifierItem::title() const { - return QString("Quassel IRC"); + +QString StatusNotifierItem::iconName() const +{ + if (state() == Passive) + return QString("quassel-inactive"); + else + return QString("quassel"); } -QString StatusNotifierItem::iconName() const { - if(state() == Passive) - return QString("quassel_inactive"); - else + +QString StatusNotifierItem::attentionIconName() const +{ + if (animationEnabled()) + return QString("quassel-message"); + else + return QString("quassel"); +} + + +QString StatusNotifierItem::toolTipIconName() const +{ return QString("quassel"); } -QString StatusNotifierItem::attentionIconName() const { - return QString("quassel_message"); + +QString StatusNotifierItem::iconThemePath() const +{ + return _iconThemePath; +} + + +QString StatusNotifierItem::menuObjectPath() const +{ + return _menuObjectPath; +} + + +void StatusNotifierItem::activated(const QPoint &pos) +{ + Q_UNUSED(pos) + activate(Trigger); } -QString StatusNotifierItem::toolTipIconName() const { - return QString("quassel"); + +bool StatusNotifierItem::eventFilter(QObject *watched, QEvent *event) +{ + if (mode() == StatusNotifier) { + //FIXME: ugly ugly workaround to weird QMenu's focus problems +#ifdef HAVE_KDE + if (watched == trayMenu() && + (event->type() == QEvent::WindowDeactivate || (event->type() == QEvent::MouseButtonRelease && static_cast(event)->button() == Qt::LeftButton))) { + // put at the back of event queue to let the action activate anyways + QTimer::singleShot(0, trayMenu(), SLOT(hide())); + } +#else + if (watched == trayMenu() && event->type() == QEvent::HoverLeave) { + trayMenu()->hide(); + } +#endif + } + return StatusNotifierItemParent::eventFilter(watched, event); } -void StatusNotifierItem::activated(const QPoint &pos) { - Q_UNUSED(pos) - activate(Trigger); + +void StatusNotifierItem::showMessage(const QString &title, const QString &message_, SystemTray::MessageIcon icon, int timeout, uint notificationId) +{ + QString message = message_; + if (_notificationsClient->isValid()) { + if (_notificationsClientSupportsMarkup) + message = Qt::escape(message); + + QStringList actions; + if (_notificationsClientSupportsActions) + actions << "activate" << "View"; + + // we always queue notifications right now + QDBusReply reply = _notificationsClient->Notify(title, 0, "quassel", title, message, actions, QVariantMap(), timeout); + if (reply.isValid()) { + uint dbusid = reply.value(); + _notificationsIdMap.insert(dbusid, notificationId); + _lastNotificationsDBusId = dbusid; + } + } + else + StatusNotifierItemParent::showMessage(title, message, icon, timeout, notificationId); } -bool StatusNotifierItem::eventFilter(QObject *watched, QEvent *event) { - if(mode() == StatusNotifier) { - //FIXME: ugly ugly workaround to weird QMenu's focus problems - if(watched == trayMenu() && - (event->type() == QEvent::WindowDeactivate || (event->type() == QEvent::MouseButtonRelease && static_cast(event)->button() == Qt::LeftButton))) { - // put at the back of event queue to let the action activate anyways - QTimer::singleShot(0, trayMenu(), SLOT(hide())); + +void StatusNotifierItem::closeMessage(uint notificationId) +{ + foreach(uint dbusid, _notificationsIdMap.keys()) { + if (_notificationsIdMap.value(dbusid) == notificationId) { + _notificationsIdMap.remove(dbusid); + _notificationsClient->CloseNotification(dbusid); + } } - } - return StatusNotifierItemParent::eventFilter(watched, event); + _lastNotificationsDBusId = 0; +} + + +void StatusNotifierItem::notificationClosed(uint dbusid, uint reason) +{ + Q_UNUSED(reason) + _lastNotificationsDBusId = 0; + emit messageClosed(_notificationsIdMap.take(dbusid)); } + +void StatusNotifierItem::notificationInvoked(uint dbusid, const QString &action) +{ + Q_UNUSED(action) + emit messageClicked(_notificationsIdMap.value(dbusid, 0)); +} + + #endif