From 13e7c603d51f22763d47e98a4e37468e46deac98 Mon Sep 17 00:00:00 2001 From: Oswald Buddenhagen Date: Thu, 29 Sep 2016 16:49:12 +0200 Subject: move dbusmenu and dbustray under themes/genericunix that's the only place where they are used, and this clarifies the structure. Change-Id: I874beb7f049b34cc73079609b8baabcc1bd3cd39 Reviewed-by: Jake Petroules --- .../themes/genericunix/dbustray/qdbustrayicon.cpp | 305 +++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 src/platformsupport/themes/genericunix/dbustray/qdbustrayicon.cpp (limited to 'src/platformsupport/themes/genericunix/dbustray/qdbustrayicon.cpp') diff --git a/src/platformsupport/themes/genericunix/dbustray/qdbustrayicon.cpp b/src/platformsupport/themes/genericunix/dbustray/qdbustrayicon.cpp new file mode 100644 index 0000000000..95e9651e9a --- /dev/null +++ b/src/platformsupport/themes/genericunix/dbustray/qdbustrayicon.cpp @@ -0,0 +1,305 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbustrayicon_p.h" + +#ifndef QT_NO_SYSTEMTRAYICON + +#include "qdbusmenuconnection_p.h" +#include "qstatusnotifieritemadaptor_p.h" +#include "qdbusmenuadaptor_p.h" +#include "qdbusplatformmenu_p.h" +#include "qxdgnotificationproxy_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Defined in Windows headers which get included by qlockfile_p.h +#undef interface + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcTray, "qt.qpa.tray") + +static const QString KDEItemFormat = QStringLiteral("org.kde.StatusNotifierItem-%1-%2"); +static const QString KDEWatcherService = QStringLiteral("org.kde.StatusNotifierWatcher"); +static const QString TempFileTemplate = QDir::tempPath() + QLatin1String("/qt-trayicon-XXXXXX.png"); +static const QString XdgNotificationService = QStringLiteral("org.freedesktop.Notifications"); +static const QString XdgNotificationPath = QStringLiteral("/org/freedesktop/Notifications"); +static const QString DefaultAction = QStringLiteral("default"); +static int instanceCount = 0; + +/*! + \class QDBusTrayIcon + \internal +*/ + +QDBusTrayIcon::QDBusTrayIcon() + : m_dbusConnection(Q_NULLPTR) + , m_adaptor(new QStatusNotifierItemAdaptor(this)) + , m_menuAdaptor(Q_NULLPTR) + , m_menu(Q_NULLPTR) + , m_notifier(Q_NULLPTR) + , m_instanceId(KDEItemFormat.arg(QCoreApplication::applicationPid()).arg(++instanceCount)) + , m_category(QStringLiteral("ApplicationStatus")) + , m_defaultStatus(QStringLiteral("Active")) // be visible all the time. QSystemTrayIcon has no API to control this. + , m_status(m_defaultStatus) + , m_tempIcon(Q_NULLPTR) + , m_tempAttentionIcon(Q_NULLPTR) + , m_registered(false) +{ + qCDebug(qLcTray); + if (instanceCount == 1) { + QDBusMenuItem::registerDBusTypes(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + } + connect(this, SIGNAL(statusChanged(QString)), m_adaptor, SIGNAL(NewStatus(QString))); + connect(this, SIGNAL(tooltipChanged()), m_adaptor, SIGNAL(NewToolTip())); + connect(this, SIGNAL(iconChanged()), m_adaptor, SIGNAL(NewIcon())); + connect(this, SIGNAL(attention()), m_adaptor, SIGNAL(NewAttentionIcon())); + connect(this, SIGNAL(attention()), m_adaptor, SIGNAL(NewTitle())); + connect(&m_attentionTimer, SIGNAL(timeout()), this, SLOT(attentionTimerExpired())); + m_attentionTimer.setSingleShot(true); +} + +QDBusTrayIcon::~QDBusTrayIcon() +{ +} + +void QDBusTrayIcon::init() +{ + qCDebug(qLcTray) << "registering" << m_instanceId; + m_registered = dBusConnection()->registerTrayIcon(this); +} + +void QDBusTrayIcon::cleanup() +{ + qCDebug(qLcTray) << "unregistering" << m_instanceId; + if (m_registered) + dBusConnection()->unregisterTrayIcon(this); + delete m_dbusConnection; + m_dbusConnection = Q_NULLPTR; + delete m_notifier; + m_notifier = Q_NULLPTR; + m_registered = false; +} + +void QDBusTrayIcon::attentionTimerExpired() +{ + m_messageTitle = QString(); + m_message = QString(); + m_attentionIcon = QIcon(); + emit attention(); + emit tooltipChanged(); + setStatus(m_defaultStatus); +} + +void QDBusTrayIcon::setStatus(const QString &status) +{ + qCDebug(qLcTray) << status; + if (m_status == status) + return; + m_status = status; + emit statusChanged(m_status); +} + +QTemporaryFile *QDBusTrayIcon::tempIcon(const QIcon &icon) +{ + // Hack for indicator-application, which doesn't handle icons sent across D-Bus: + // save the icon to a temp file and set the icon name to that filename. + static bool necessity_checked = false; + static bool necessary = false; + if (!necessity_checked) { + QDBusConnection session = QDBusConnection::sessionBus(); + uint pid = session.interface()->servicePid(KDEWatcherService).value(); + QString processName = QLockFilePrivate::processNameByPid(pid); + necessary = processName.endsWith(QLatin1String("indicator-application-service")); + necessity_checked = true; + } + if (!necessary) + return Q_NULLPTR; + qreal dpr = qGuiApp->devicePixelRatio(); + QTemporaryFile *ret = new QTemporaryFile(TempFileTemplate, this); + ret->open(); + icon.pixmap(QSize(22 * dpr, 22 * dpr)).save(ret); + ret->close(); + return ret; +} + +QDBusMenuConnection * QDBusTrayIcon::dBusConnection() +{ + if (!m_dbusConnection) { + m_dbusConnection = new QDBusMenuConnection(this, m_instanceId); + m_notifier = new QXdgNotificationInterface(XdgNotificationService, + XdgNotificationPath, m_dbusConnection->connection(), this); + connect(m_notifier, SIGNAL(NotificationClosed(uint,uint)), this, SLOT(notificationClosed(uint,uint))); + connect(m_notifier, SIGNAL(ActionInvoked(uint,QString)), this, SLOT(actionInvoked(uint,QString))); + } + return m_dbusConnection; +} + +void QDBusTrayIcon::updateIcon(const QIcon &icon) +{ + m_iconName = icon.name(); + m_icon = icon; + if (m_iconName.isEmpty()) { + if (m_tempIcon) + delete m_tempIcon; + m_tempIcon = tempIcon(icon); + if (m_tempIcon) + m_iconName = m_tempIcon->fileName(); + } + qCDebug(qLcTray) << m_iconName << icon.availableSizes(); + emit iconChanged(); +} + +void QDBusTrayIcon::updateToolTip(const QString &tooltip) +{ + qCDebug(qLcTray) << tooltip; + m_tooltip = tooltip; + emit tooltipChanged(); +} + +QPlatformMenu *QDBusTrayIcon::createMenu() const +{ + return new QDBusPlatformMenu(); +} + +void QDBusTrayIcon::updateMenu(QPlatformMenu * menu) +{ + qCDebug(qLcTray) << menu; + bool needsRegistering = !m_menu; + if (!m_menu) + m_menu = qobject_cast(menu); + if (!m_menuAdaptor) { + m_menuAdaptor = new QDBusMenuAdaptor(m_menu); + // TODO connect(m_menu, , m_menuAdaptor, SIGNAL(ItemActivationRequested(int,uint))); + connect(m_menu, SIGNAL(propertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList)), + m_menuAdaptor, SIGNAL(ItemsPropertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList))); + connect(m_menu, SIGNAL(updated(uint,int)), + m_menuAdaptor, SIGNAL(LayoutUpdated(uint,int))); + } + m_menu->emitUpdated(); + if (needsRegistering) + dBusConnection()->registerTrayIconMenu(this); +} + +void QDBusTrayIcon::showMessage(const QString &title, const QString &msg, const QIcon &icon, + QPlatformSystemTrayIcon::MessageIcon iconType, int msecs) +{ + m_messageTitle = title; + m_message = msg; + m_attentionIcon = icon; + QStringList notificationActions; + switch (iconType) { + case Information: + m_attentionIconName = QStringLiteral("dialog-information"); + break; + case Warning: + m_attentionIconName = QStringLiteral("dialog-warning"); + break; + case Critical: + m_attentionIconName = QStringLiteral("dialog-error"); + // If there are actions, the desktop notification may appear as a message dialog + // with button(s), which will interrupt the user and require a response. + // That is an optional feature in implementations of org.freedesktop.Notifications + notificationActions << DefaultAction << tr("OK"); + break; + default: + m_attentionIconName.clear(); + break; + } + if (m_attentionIconName.isEmpty()) { + if (m_tempAttentionIcon) + delete m_tempAttentionIcon; + m_tempAttentionIcon = tempIcon(icon); + if (m_tempAttentionIcon) + m_attentionIconName = m_tempAttentionIcon->fileName(); + } + qCDebug(qLcTray) << title << msg << + QPlatformSystemTrayIcon::metaObject()->enumerator( + QPlatformSystemTrayIcon::staticMetaObject.indexOfEnumerator("MessageIcon")).valueToKey(iconType) + << m_attentionIconName << msecs; + setStatus(QStringLiteral("NeedsAttention")); + m_attentionTimer.start(msecs); + emit tooltipChanged(); + emit attention(); + + // Desktop notification + QVariantMap hints; + // urgency levels according to https://developer.gnome.org/notification-spec/#urgency-levels + // 0 low, 1 normal, 2 critical + int urgency = static_cast(iconType) - 1; + if (urgency < 0) // no icon + urgency = 0; + hints.insert(QLatin1String("urgency"), QVariant(urgency)); + m_notifier->notify(QCoreApplication::applicationName(), 0, + m_attentionIconName, title, msg, notificationActions, hints, msecs); +} + +void QDBusTrayIcon::actionInvoked(uint id, const QString &action) +{ + qCDebug(qLcTray) << id << action; + emit messageClicked(); +} + +void QDBusTrayIcon::notificationClosed(uint id, uint reason) +{ + qCDebug(qLcTray) << id << reason; +} + +bool QDBusTrayIcon::isSystemTrayAvailable() const +{ + QDBusMenuConnection * conn = const_cast(this)->dBusConnection(); + qCDebug(qLcTray) << conn->isStatusNotifierHostRegistered(); + return conn->isStatusNotifierHostRegistered(); +} + +QT_END_NAMESPACE +#endif //QT_NO_SYSTEMTRAYICON -- cgit v1.2.3