Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qdbustrayicon.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qdbustrayicon_p.h"
5
6#ifndef QT_NO_SYSTEMTRAYICON
7
8#include <QString>
9#include <QDebug>
10#include <QRect>
11#include <QLoggingCategory>
12#include <QStandardPaths>
13#include <QFileInfo>
14#include <QDir>
15#include <QMetaObject>
16#include <QMetaEnum>
17#include <QDBusConnectionInterface>
18#include <QDBusArgument>
19#include <QDBusMetaType>
20#include <QDBusServiceWatcher>
21
22#include <qpa/qplatformmenu.h>
23#include <qpa/qplatformintegration.h>
24#include <qpa/qplatformservices.h>
25
26#include <private/qdbusmenuconnection_p.h>
27#include <private/qstatusnotifieritemadaptor_p.h>
28#include <private/qdbusmenuadaptor_p.h>
29#include <private/qdbusplatformmenu_p.h>
30#include <private/qxdgnotificationproxy_p.h>
31#include <private/qlockfile_p.h>
32#include <private/qguiapplication_p.h>
33
34// Defined in Windows headers which get included by qlockfile_p.h
35#undef interface
36
38
39using namespace Qt::StringLiterals;
40
41Q_LOGGING_CATEGORY(qLcTray, "qt.qpa.tray")
42
44{
46 if (!tempPath.isEmpty()) {
47 QString flatpakId = qEnvironmentVariable("FLATPAK_ID");
48 if (!flatpakId.isEmpty() && QFileInfo::exists("/.flatpak-info"_L1))
49 tempPath += "/app/"_L1 + flatpakId;
50 return tempPath;
51 }
52
54
55 if (!tempPath.isEmpty()) {
56 QDir tempDir(tempPath);
57 if (tempDir.exists())
58 return tempPath;
59
60 if (tempDir.mkpath(QStringLiteral("."))) {
61 const QFile::Permissions permissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
62 if (QFile(tempPath).setPermissions(permissions))
63 return tempPath;
64 }
65 }
66
67 return QDir::tempPath();
68}
69
70static const QString KDEItemFormat = QStringLiteral("org.kde.StatusNotifierItem-%1-%2");
71static const QString KDEWatcherService = QStringLiteral("org.kde.StatusNotifierWatcher");
72static const QString XdgNotificationService = QStringLiteral("org.freedesktop.Notifications");
73static const QString XdgNotificationPath = QStringLiteral("/org/freedesktop/Notifications");
74static const QString DefaultAction = QStringLiteral("default");
75static int instanceCount = 0;
76
78{
79 static const QString TempFileTemplate = iconTempPath() + "/qt-trayicon-XXXXXX.png"_L1;
80 return TempFileTemplate;
81}
82
89 : m_dbusConnection(nullptr)
90 , m_adaptor(new QStatusNotifierItemAdaptor(this))
91 , m_menuAdaptor(nullptr)
92 , m_menu(nullptr)
93 , m_notifier(nullptr)
94 , m_instanceId(KDEItemFormat.arg(QCoreApplication::applicationPid()).arg(++instanceCount))
95 , m_category(QStringLiteral("ApplicationStatus"))
96 , m_defaultStatus(QStringLiteral("Active")) // be visible all the time. QSystemTrayIcon has no API to control this.
97 , m_status(m_defaultStatus)
98 , m_tempIcon(nullptr)
99 , m_tempAttentionIcon(nullptr)
100 , m_registered(false)
101{
102 qCDebug(qLcTray);
103 if (instanceCount == 1) {
105 qDBusRegisterMetaType<QXdgDBusImageStruct>();
106 qDBusRegisterMetaType<QXdgDBusImageVector>();
107 qDBusRegisterMetaType<QXdgDBusToolTipStruct>();
108 }
109 connect(this, SIGNAL(statusChanged(QString)), m_adaptor, SIGNAL(NewStatus(QString)));
110 connect(this, SIGNAL(tooltipChanged()), m_adaptor, SIGNAL(NewToolTip()));
111 connect(this, SIGNAL(iconChanged()), m_adaptor, SIGNAL(NewIcon()));
112 connect(this, SIGNAL(attention()), m_adaptor, SIGNAL(NewAttentionIcon()));
113 connect(this, SIGNAL(menuChanged()), m_adaptor, SIGNAL(NewMenu()));
114 connect(this, SIGNAL(attention()), m_adaptor, SIGNAL(NewTitle()));
115 connect(&m_attentionTimer, SIGNAL(timeout()), this, SLOT(attentionTimerExpired()));
116 m_attentionTimer.setSingleShot(true);
117}
118
122
124{
125 qCDebug(qLcTray) << "registering" << m_instanceId;
126 m_registered = dBusConnection()->registerTrayIcon(this);
128 this, &QDBusTrayIcon::watcherServiceRegistered);
129}
130
132{
133 qCDebug(qLcTray) << "unregistering" << m_instanceId;
134 if (m_registered)
136 delete m_dbusConnection;
137 m_dbusConnection = nullptr;
138 delete m_notifier;
139 m_notifier = nullptr;
140 m_registered = false;
141}
142
143void QDBusTrayIcon::watcherServiceRegistered(const QString &serviceName)
144{
145 Q_UNUSED(serviceName);
146 // We have the icon registered, but the watcher has restarted or
147 // changed, so we need to tell it about our icon again
148 if (m_registered)
150}
151
152void QDBusTrayIcon::attentionTimerExpired()
153{
154 m_messageTitle = QString();
155 m_message = QString();
156 m_attentionIcon = QIcon();
157 emit attention();
159 setStatus(m_defaultStatus);
160}
161
162void QDBusTrayIcon::setStatus(const QString &status)
163{
164 qCDebug(qLcTray) << status;
165 if (m_status == status)
166 return;
167 m_status = status;
168 emit statusChanged(m_status);
169}
170
171QTemporaryFile *QDBusTrayIcon::tempIcon(const QIcon &icon)
172{
173 // Hack for indicator-application, which doesn't handle icons sent across D-Bus:
174 // save the icon to a temp file and set the icon name to that filename.
175 static bool necessity_checked = false;
176 static bool necessary = false;
177 if (!necessity_checked) {
179 uint pid = session.interface()->servicePid(KDEWatcherService).value();
181 necessary = processName.endsWith("indicator-application-service"_L1);
182 if (!necessary) {
183 necessary = session.interface()->isServiceRegistered(
184 QStringLiteral("com.canonical.indicator.application"));
185 }
186 if (!necessary) {
187 necessary = session.interface()->isServiceRegistered(
188 QStringLiteral("org.ayatana.indicator.application"));
189 }
190 if (!necessary && QGuiApplication::desktopSettingsAware()) {
191 // Accessing to process name might be not allowed if the application
192 // is confined, thus we can just rely on the current desktop in use
194 necessary = services->desktopEnvironment().split(':').contains("UNITY");
195 }
196 necessity_checked = true;
197 }
198 if (!necessary)
199 return nullptr;
200 QTemporaryFile *ret = new QTemporaryFile(tempFileTemplate(), this);
201 if (!ret->open()) {
202 delete ret;
203 return nullptr;
204 }
205 icon.pixmap(QSize(22, 22)).save(ret);
206 ret->close();
207 return ret;
208}
209
211{
212 if (!m_dbusConnection) {
213 m_dbusConnection = new QDBusMenuConnection(this, m_instanceId);
215 XdgNotificationPath, m_dbusConnection->connection(), this);
216 connect(m_notifier, SIGNAL(NotificationClosed(uint,uint)), this, SLOT(notificationClosed(uint,uint)));
217 connect(m_notifier, SIGNAL(ActionInvoked(uint,QString)), this, SLOT(actionInvoked(uint,QString)));
218 }
219 return m_dbusConnection;
220}
221
223{
224 m_iconName = icon.name();
225 m_icon = icon;
226 if (m_iconName.isEmpty()) {
227 if (m_tempIcon)
228 delete m_tempIcon;
229 m_tempIcon = tempIcon(icon);
230 if (m_tempIcon)
231 m_iconName = m_tempIcon->fileName();
232 }
233 qCDebug(qLcTray) << m_iconName << icon.availableSizes();
235}
236
238{
239 qCDebug(qLcTray) << tooltip;
240 m_tooltip = tooltip;
242}
243
245{
246 return new QDBusPlatformMenu();
247}
248
250{
251 qCDebug(qLcTray) << menu;
252 QDBusPlatformMenu *newMenu = qobject_cast<QDBusPlatformMenu *>(menu);
253 if (m_menu != newMenu) {
254 if (m_menu) {
256 delete m_menuAdaptor;
257 }
258 m_menu = newMenu;
259 m_menuAdaptor = new QDBusMenuAdaptor(m_menu);
260 // TODO connect(m_menu, , m_menuAdaptor, SIGNAL(ItemActivationRequested(int,uint)));
261 connect(m_menu, SIGNAL(propertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList)),
262 m_menuAdaptor, SIGNAL(ItemsPropertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList)));
263 connect(m_menu, SIGNAL(updated(uint,int)),
264 m_menuAdaptor, SIGNAL(LayoutUpdated(uint,int)));
267 }
268}
269
271 QPlatformSystemTrayIcon::MessageIcon iconType, int msecs)
272{
273 m_messageTitle = title;
274 m_message = msg;
275 m_attentionIcon = icon;
276 QStringList notificationActions;
277 switch (iconType) {
278 case Information:
279 m_attentionIconName = QStringLiteral("dialog-information");
280 break;
281 case Warning:
282 m_attentionIconName = QStringLiteral("dialog-warning");
283 break;
284 case Critical:
285 m_attentionIconName = QStringLiteral("dialog-error");
286 // If there are actions, the desktop notification may appear as a message dialog
287 // with button(s), which will interrupt the user and require a response.
288 // That is an optional feature in implementations of org.freedesktop.Notifications
289 notificationActions << DefaultAction << tr("OK");
290 break;
291 default:
292 m_attentionIconName.clear();
293 break;
294 }
295 if (m_attentionIconName.isEmpty()) {
296 if (m_tempAttentionIcon)
297 delete m_tempAttentionIcon;
298 m_tempAttentionIcon = tempIcon(icon);
299 if (m_tempAttentionIcon)
300 m_attentionIconName = m_tempAttentionIcon->fileName();
301 }
302 qCDebug(qLcTray) << title << msg <<
303 QPlatformSystemTrayIcon::metaObject()->enumerator(
304 QPlatformSystemTrayIcon::staticMetaObject.indexOfEnumerator("MessageIcon")).valueToKey(iconType)
305 << m_attentionIconName << msecs;
306 setStatus(QStringLiteral("NeedsAttention"));
307 m_attentionTimer.start(msecs);
309 emit attention();
310
311 // Desktop notification
312 QVariantMap hints;
313 // urgency levels according to https://developer.gnome.org/notification-spec/#urgency-levels
314 // 0 low, 1 normal, 2 critical
315 int urgency = static_cast<int>(iconType) - 1;
316 if (urgency < 0) // no icon
317 urgency = 0;
318 hints.insert("urgency"_L1, QVariant(urgency));
320 m_attentionIconName, title, msg, notificationActions, hints, msecs);
321}
322
323void QDBusTrayIcon::actionInvoked(uint id, const QString &action)
324{
325 qCDebug(qLcTray) << id << action;
327}
328
329void QDBusTrayIcon::notificationClosed(uint id, uint reason)
330{
331 qCDebug(qLcTray) << id << reason;
332}
333
335{
336 QDBusMenuConnection * conn = const_cast<QDBusTrayIcon *>(this)->dBusConnection();
337
338 // If the KDE watcher service is registered, we must be on a desktop
339 // where a StatusNotifier-conforming system tray exists.
340 qCDebug(qLcTray) << conn->isWatcherRegistered();
341 return conn->isWatcherRegistered();
342}
343
345
346#include "moc_qdbustrayicon_p.cpp"
347#endif //QT_NO_SYSTEMTRAYICON
std::vector< ObjCStrongReference< CBMutableService > > services
\inmodule QtCore
QString applicationName
the name of this application
QDBusReply< bool > isServiceRegistered(const QString &serviceName) const
Returns true if the service name serviceName has is currently registered.
QDBusReply< uint > servicePid(const QString &serviceName) const
Returns the Unix Process ID (PID) for the process currently holding the bus service serviceName.
\inmodule QtDBus
QDBusConnectionInterface * interface() const
Returns a QDBusConnectionInterface object that represents the D-Bus server interface on this connecti...
static QDBusConnection sessionBus()
Returns a QDBusConnection object opened with the session bus.
bool registerTrayIconMenu(QDBusTrayIcon *item)
bool registerTrayIcon(QDBusTrayIcon *item)
void unregisterTrayIconMenu(QDBusTrayIcon *item)
void unregisterTrayIcon(QDBusTrayIcon *item)
bool registerTrayIconWithWatcher(QDBusTrayIcon *item)
QDBusConnection connection() const
static void registerDBusTypes()
void serviceRegistered(const QString &service)
This signal is emitted whenever this object detects that the service serviceName became available on ...
void init() override
This method is called to initialize the platform dependent implementation.
void updateMenu(QPlatformMenu *menu) override
This method is called when the system tray menu did change.
void menuChanged()
void statusChanged(QString arg)
QDBusPlatformMenu * menu
void showMessage(const QString &title, const QString &msg, const QIcon &icon, MessageIcon iconType, int msecs) override
Shows a balloon message for the entry with the given title, message msg and icon for the time specifi...
QDBusMenuConnection * dBusConnection()
void cleanup() override
This method is called to cleanup the platform dependent implementation.
virtual ~QDBusTrayIcon()
void attention()
void tooltipChanged()
void updateIcon(const QIcon &icon) override
This method is called when the icon did change.
void iconChanged()
void updateToolTip(const QString &tooltip) override
This method is called when the tooltip text did change.
bool isSystemTrayAvailable() const override
Returns true if the system tray is available on the platform.
QPlatformMenu * createMenu() const override
This method allows platforms to use a different QPlatformMenu for system tray menus than what would n...
\inmodule QtCore
Definition qdir.h:20
static QString tempPath()
Returns the absolute canonical path of the system's temporary directory.
Definition qdir.cpp:2133
bool exists() const
Returns true if the file system entry this QFileInfo refers to exists; otherwise returns false.
\inmodule QtCore
Definition qfile.h:93
static QPlatformIntegration * platformIntegration()
static bool desktopSettingsAware()
Returns true if Qt is set to use the system's standard colors, fonts, etc.; otherwise returns false.
The QIcon class provides scalable icons in different modes and states.
Definition qicon.h:20
QList< QSize > availableSizes(Mode mode=Normal, State state=Off) const
Definition qicon.cpp:1144
QString name() const
Definition qicon.cpp:1161
QPixmap pixmap(const QSize &size, Mode mode=Normal, State state=Off) const
Returns a pixmap with the requested size, mode, and state, generating one if necessary.
Definition qicon.cpp:834
static Q_CORE_EXPORT QString processNameByPid(qint64 pid)
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
bool save(const QString &fileName, const char *format=nullptr, int quality=-1) const
Saves the pixmap to the file with the given fileName using the specified image file format and qualit...
Definition qpixmap.cpp:803
The QPlatformServices provides the backend for desktop-related functionality.
MessageIcon
This enum describes the icon that is shown when a balloon message is displayed.
void messageClicked()
This signal is emitted when the message displayed using showMessage() was clicked by the user.
\inmodule QtCore
Definition qsize.h:25
static QString writableLocation(StandardLocation type)
\inmodule QtCore
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1252
void setSingleShot(bool singleShot)
Definition qtimer.cpp:552
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:241
\inmodule QtCore
Definition qvariant.h:65
QDBusPendingReply< uint > notify(const QString &appName, uint replacesId, const QString &appIcon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout)
#define this
Definition dialogs.cpp:9
Combined button and popup list for selecting options.
QList< QDBusMenuItemKeys > QDBusMenuItemKeysList
QList< QDBusMenuItem > QDBusMenuItemList
static const QString DefaultAction
static int instanceCount
static const QString KDEWatcherService
static const QString XdgNotificationService
static const QString XdgNotificationPath
static QString tempFileTemplate()
static QString iconTempPath()
static const QString KDEItemFormat
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
return ret
#define SLOT(a)
Definition qobjectdefs.h:52
#define SIGNAL(a)
Definition qobjectdefs.h:53
GLbitfield GLuint64 timeout
[4]
SSL_CTX int void * arg
#define QStringLiteral(str)
#define tr(X)
QString qEnvironmentVariable(const char *varName, const QString &defaultValue)
#define emit
#define Q_UNUSED(x)
unsigned int uint
Definition qtypes.h:34
QObject::connect nullptr
QString title
[35]
QMenu menu
[5]