Qt 6.x
The Qt SDK
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
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
120{
121}
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;
201 ret->open();
202 icon.pixmap(QSize(22, 22)).save(ret);
203 ret->close();
204 return ret;
205}
206
208{
209 if (!m_dbusConnection) {
210 m_dbusConnection = new QDBusMenuConnection(this, m_instanceId);
212 XdgNotificationPath, m_dbusConnection->connection(), this);
213 connect(m_notifier, SIGNAL(NotificationClosed(uint,uint)), this, SLOT(notificationClosed(uint,uint)));
214 connect(m_notifier, SIGNAL(ActionInvoked(uint,QString)), this, SLOT(actionInvoked(uint,QString)));
215 }
216 return m_dbusConnection;
217}
218
220{
221 m_iconName = icon.name();
222 m_icon = icon;
223 if (m_iconName.isEmpty()) {
224 if (m_tempIcon)
225 delete m_tempIcon;
226 m_tempIcon = tempIcon(icon);
227 if (m_tempIcon)
228 m_iconName = m_tempIcon->fileName();
229 }
230 qCDebug(qLcTray) << m_iconName << icon.availableSizes();
232}
233
235{
236 qCDebug(qLcTray) << tooltip;
237 m_tooltip = tooltip;
239}
240
242{
243 return new QDBusPlatformMenu();
244}
245
247{
248 qCDebug(qLcTray) << menu;
249 QDBusPlatformMenu *newMenu = qobject_cast<QDBusPlatformMenu *>(menu);
250 if (m_menu != newMenu) {
251 if (m_menu) {
253 delete m_menuAdaptor;
254 }
255 m_menu = newMenu;
256 m_menuAdaptor = new QDBusMenuAdaptor(m_menu);
257 // TODO connect(m_menu, , m_menuAdaptor, SIGNAL(ItemActivationRequested(int,uint)));
258 connect(m_menu, SIGNAL(propertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList)),
259 m_menuAdaptor, SIGNAL(ItemsPropertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList)));
260 connect(m_menu, SIGNAL(updated(uint,int)),
261 m_menuAdaptor, SIGNAL(LayoutUpdated(uint,int)));
264 }
265}
266
268 QPlatformSystemTrayIcon::MessageIcon iconType, int msecs)
269{
270 m_messageTitle = title;
271 m_message = msg;
272 m_attentionIcon = icon;
273 QStringList notificationActions;
274 switch (iconType) {
275 case Information:
276 m_attentionIconName = QStringLiteral("dialog-information");
277 break;
278 case Warning:
279 m_attentionIconName = QStringLiteral("dialog-warning");
280 break;
281 case Critical:
282 m_attentionIconName = QStringLiteral("dialog-error");
283 // If there are actions, the desktop notification may appear as a message dialog
284 // with button(s), which will interrupt the user and require a response.
285 // That is an optional feature in implementations of org.freedesktop.Notifications
286 notificationActions << DefaultAction << tr("OK");
287 break;
288 default:
289 m_attentionIconName.clear();
290 break;
291 }
292 if (m_attentionIconName.isEmpty()) {
293 if (m_tempAttentionIcon)
294 delete m_tempAttentionIcon;
295 m_tempAttentionIcon = tempIcon(icon);
296 if (m_tempAttentionIcon)
297 m_attentionIconName = m_tempAttentionIcon->fileName();
298 }
299 qCDebug(qLcTray) << title << msg <<
300 QPlatformSystemTrayIcon::metaObject()->enumerator(
301 QPlatformSystemTrayIcon::staticMetaObject.indexOfEnumerator("MessageIcon")).valueToKey(iconType)
302 << m_attentionIconName << msecs;
303 setStatus(QStringLiteral("NeedsAttention"));
304 m_attentionTimer.start(msecs);
306 emit attention();
307
308 // Desktop notification
309 QVariantMap hints;
310 // urgency levels according to https://developer.gnome.org/notification-spec/#urgency-levels
311 // 0 low, 1 normal, 2 critical
312 int urgency = static_cast<int>(iconType) - 1;
313 if (urgency < 0) // no icon
314 urgency = 0;
315 hints.insert("urgency"_L1, QVariant(urgency));
317 m_attentionIconName, title, msg, notificationActions, hints, msecs);
318}
319
320void QDBusTrayIcon::actionInvoked(uint id, const QString &action)
321{
322 qCDebug(qLcTray) << id << action;
324}
325
326void QDBusTrayIcon::notificationClosed(uint id, uint reason)
327{
328 qCDebug(qLcTray) << id << reason;
329}
330
332{
333 QDBusMenuConnection * conn = const_cast<QDBusTrayIcon *>(this)->dBusConnection();
334
335 // If the KDE watcher service is registered, we must be on a desktop
336 // where a StatusNotifier-conforming system tray exists.
337 qCDebug(qLcTray) << conn->isWatcherRegistered();
338 return conn->isWatcherRegistered();
339}
340
342
343#include "moc_qdbustrayicon_p.cpp"
344#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:19
bool exists() const
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qdir.cpp:1715
bool mkpath(const QString &dirPath) const
Creates the directory path dirPath.
Definition qdir.cpp:1579
static QString tempPath()
Returns the absolute canonical path of the system's temporary directory.
Definition qdir.cpp:2130
bool exists() const
Returns true if the file 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:1098
QString name() const
Definition qicon.cpp:1115
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:788
Definition qlist.h:74
static Q_CORE_EXPORT QString processNameByPid(qint64 pid)
iterator insert(const Key &key, const T &value)
Definition qmap.h:687
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2823
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:807
virtual QPlatformServices * services() const
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:127
void clear()
Clears the contents of the string and makes it null.
Definition qstring.h:1107
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition qstring.cpp:5350
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
\inmodule QtCore \reentrant
QString fileName() const override
Returns the complete unique filename backing the QTemporaryFile object.
void setSingleShot(bool singleShot)
Definition qtimer.cpp:580
void start(int msec)
Starts or restarts the timer with a timeout interval of msec milliseconds.
Definition qtimer.cpp:208
\inmodule QtCore
Definition qvariant.h:64
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.
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:51
#define SIGNAL(a)
Definition qobjectdefs.h:52
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:29
QObject::connect nullptr
QString title
[35]
QMenu menu
[5]