Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qwindowssystemtrayicon.cpp
Go to the documentation of this file.
1// Copyright (C) 2017 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 <QtCore/qt_windows.h>
5
7#include "qwindowscontext.h"
8#include "qwindowstheme.h"
9#include "qwindowsmenu.h"
10#include "qwindowsscreen.h"
11
12#include <QtGui/qguiapplication.h>
13#include <QtGui/qpixmap.h>
14#include <QtCore/qdebug.h>
15#include <QtCore/qlist.h>
16#include <QtCore/qrect.h>
17#include <QtCore/qsettings.h>
18#include <qpa/qwindowsysteminterface.h>
19
20#include <commctrl.h>
21#include <shellapi.h>
22#include <shlobj.h>
23#include <windowsx.h>
24
26
27static const UINT q_uNOTIFYICONID = 0;
28
30#define MYWM_NOTIFYICON (WM_APP+101)
31
32Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &);
33
34// Copy QString data to a limited wchar_t array including \0.
35static inline void qStringToLimitedWCharArray(QString in, wchar_t *target, int maxLength)
36{
37 const int length = qMin(maxLength - 1, in.size());
38 if (length < in.size())
39 in.truncate(length);
40 in.toWCharArray(target);
41 target[length] = wchar_t(0);
42}
43
44static inline void initNotifyIconData(NOTIFYICONDATA &tnd)
45{
46 memset(&tnd, 0, sizeof(NOTIFYICONDATA));
47 tnd.cbSize = sizeof(NOTIFYICONDATA);
48 tnd.uVersion = NOTIFYICON_VERSION_4;
49}
50
51static void setIconContents(NOTIFYICONDATA &tnd, const QString &tip, HICON hIcon)
52{
53 tnd.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP;
54 tnd.uCallbackMessage = MYWM_NOTIFYICON;
55 tnd.hIcon = hIcon;
56 qStringToLimitedWCharArray(tip, tnd.szTip, sizeof(tnd.szTip) / sizeof(wchar_t));
57}
58
59static void setIconVisibility(NOTIFYICONDATA &tnd, bool v)
60{
61 tnd.uFlags |= NIF_STATE;
62 tnd.dwStateMask = NIS_HIDDEN;
63 tnd.dwState = v ? 0 : NIS_HIDDEN;
64}
65
66// Match the HWND of the dummy window to the instances
68{
69 HWND hwnd;
71};
72
74
75Q_GLOBAL_STATIC(HwndTrayIconEntries, hwndTrayIconEntries)
76
77static int indexOfHwnd(HWND hwnd)
78{
79 const HwndTrayIconEntries *entries = hwndTrayIconEntries();
80 for (int i = 0, size = entries->size(); i < size; ++i) {
81 if (entries->at(i).hwnd == hwnd)
82 return i;
83 }
84 return -1;
85}
86
87extern "C" LRESULT QT_WIN_CALLBACK qWindowsTrayIconWndProc(HWND hwnd, UINT message,
88 WPARAM wParam, LPARAM lParam)
89{
91 || message == WM_INITMENU || message == WM_INITMENUPOPUP
92 || message == WM_CLOSE || message == WM_COMMAND) {
93 const int index = indexOfHwnd(hwnd);
94 if (index >= 0) {
95 MSG msg;
96 msg.hwnd = hwnd; // re-create MSG structure
97 msg.message = message; // time and pt fields ignored
98 msg.wParam = wParam;
99 msg.lParam = lParam;
100 msg.pt.x = GET_X_LPARAM(lParam);
101 msg.pt.y = GET_Y_LPARAM(lParam);
102 long result = 0;
103 if (hwndTrayIconEntries()->at(index).trayIcon->winEvent(msg, &result))
104 return result;
105 }
106 }
107 return DefWindowProc(hwnd, message, wParam, lParam);
108}
109
110// Note: Message windows (HWND_MESSAGE) are not sufficient, they
111// will not receive the "TaskbarCreated" message.
112static inline HWND createTrayIconMessageWindow()
113{
115 if (!ctx)
116 return nullptr;
117 // Register window class in the platform plugin.
118 const QString className =
119 ctx->registerWindowClass(QWindowsContext::classNamePrefix() + QStringLiteral("TrayIconMessageWindowClass"),
121 const wchar_t windowName[] = L"QTrayIconMessageWindow";
122 return CreateWindowEx(0, reinterpret_cast<const wchar_t *>(className.utf16()),
123 windowName, WS_OVERLAPPED,
124 CW_USEDEFAULT, CW_USEDEFAULT,
125 CW_USEDEFAULT, CW_USEDEFAULT,
126 nullptr, nullptr,
127 static_cast<HINSTANCE>(GetModuleHandle(nullptr)), nullptr);
128}
129
138{
139}
140
142{
143 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this;
144 ensureCleanup();
145}
146
148{
149 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this;
150 m_visible = true;
151 if (!setIconVisible(m_visible))
152 ensureInstalled();
153}
154
156{
157 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this;
158 m_visible = false;
159 ensureCleanup();
160}
161
163{
164 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this;
165 if (icon.cacheKey() == m_icon.cacheKey())
166 return;
167 m_icon = icon;
168 const HICON hIconToDestroy = createIcon(icon);
169 if (ensureInstalled())
170 sendTrayMessage(NIM_MODIFY);
171 if (hIconToDestroy)
172 DestroyIcon(hIconToDestroy);
173}
174
176{
177 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << tooltip << ')' << this;
178 if (m_toolTip == tooltip)
179 return;
180 m_toolTip = tooltip;
181 if (isInstalled())
182 sendTrayMessage(NIM_MODIFY);
183}
184
186{
187 NOTIFYICONIDENTIFIER nid;
188 memset(&nid, 0, sizeof(nid));
189 nid.cbSize = sizeof(nid);
190 nid.hWnd = m_hwnd;
191 nid.uID = q_uNOTIFYICONID;
192 RECT rect;
193 const QRect result = SUCCEEDED(Shell_NotifyIconGetRect(&nid, &rect))
194 ? QRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)
195 : QRect();
196 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "returns" << result;
197 return result;
198}
199
201 const QIcon &icon,
203 int msecsIn)
204{
205 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << title << messageIn << icon
206 << iconType << msecsIn << ')' << this;
207 if (!supportsMessages())
208 return;
209 // For empty messages, ensures that they show when only title is set
210 QString message = messageIn;
211 if (message.isEmpty() && !title.isEmpty())
212 message.append(u' ');
213
214 NOTIFYICONDATA tnd;
216 qStringToLimitedWCharArray(message, tnd.szInfo, 256);
217 qStringToLimitedWCharArray(title, tnd.szInfoTitle, 64);
218
219 tnd.uID = q_uNOTIFYICONID;
220 tnd.dwInfoFlags = NIIF_USER;
221
222 QSize size(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
223 const QSize largeIcon(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
224 const QSize more = icon.actualSize(largeIcon);
225 if (more.height() > (largeIcon.height() * 3/4) || more.width() > (largeIcon.width() * 3/4)) {
226 tnd.dwInfoFlags |= NIIF_LARGE_ICON;
227 size = largeIcon;
228 }
229 QPixmap pm = icon.pixmap(size);
230 if (pm.isNull()) {
231 tnd.dwInfoFlags = NIIF_INFO;
232 } else {
233 if (pm.size() != size) {
234 qWarning("QSystemTrayIcon::showMessage: Wrong icon size (%dx%d), please add standard one: %dx%d",
235 pm.size().width(), pm.size().height(), size.width(), size.height());
237 }
238 tnd.hBalloonIcon = qt_pixmapToWinHICON(pm);
239 }
240 tnd.hWnd = m_hwnd;
241 tnd.uTimeout = msecsIn <= 0 ? UINT(10000) : UINT(msecsIn); // 10s default
242 tnd.uFlags = NIF_INFO | NIF_SHOWTIP;
243
244 Shell_NotifyIcon(NIM_MODIFY, &tnd);
245}
246
248{
249 // The key does typically not exist on Windows 10, default to true.
250 return QWindowsContext::readAdvancedExplorerSettings(L"EnableBalloonTips", 1) != 0;
251}
252
254{
255 if (QWindowsTheme::useNativeMenus() && m_menu.isNull())
256 m_menu = new QWindowsPopupMenu;
257 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "returns" << m_menu.data();
258 return m_menu.data();
259}
260
261// Delay-install until an Icon exists
262bool QWindowsSystemTrayIcon::ensureInstalled()
263{
264 if (isInstalled())
265 return true;
266 if (m_hIcon == nullptr)
267 return false;
269 if (Q_UNLIKELY(m_hwnd == nullptr))
270 return false;
271 // For restoring the tray icon after explorer crashes
273 MYWM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
274 // Allow the WM_TASKBARCREATED message through the UIPI filter
275 ChangeWindowMessageFilterEx(m_hwnd, MYWM_TASKBARCREATED, MSGFLT_ALLOW, nullptr);
276 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "MYWM_TASKBARCREATED=" << MYWM_TASKBARCREATED;
277
279 hwndTrayIconEntries()->append(entry);
280 sendTrayMessage(NIM_ADD);
281 return true;
282}
283
284void QWindowsSystemTrayIcon::ensureCleanup()
285{
286 if (isInstalled()) {
287 const int index = indexOfHwnd(m_hwnd);
288 if (index >= 0)
289 hwndTrayIconEntries()->removeAt(index);
290 sendTrayMessage(NIM_DELETE);
291 DestroyWindow(m_hwnd);
292 m_hwnd = nullptr;
293 }
294 if (m_hIcon != nullptr)
295 DestroyIcon(m_hIcon);
296 m_hIcon = nullptr;
297 m_menu = nullptr; // externally owned
298 m_toolTip.clear();
299}
300
301bool QWindowsSystemTrayIcon::setIconVisible(bool visible)
302{
303 if (!isInstalled())
304 return false;
305 NOTIFYICONDATA tnd;
307 tnd.uID = q_uNOTIFYICONID;
308 tnd.hWnd = m_hwnd;
309 setIconVisibility(tnd, visible);
310 return Shell_NotifyIcon(NIM_MODIFY, &tnd) == TRUE;
311}
312
313bool QWindowsSystemTrayIcon::sendTrayMessage(DWORD msg)
314{
315 NOTIFYICONDATA tnd;
317 tnd.uID = q_uNOTIFYICONID;
318 tnd.hWnd = m_hwnd;
319 tnd.uFlags = NIF_SHOWTIP;
320 if (msg != NIM_DELETE && !m_visible)
321 setIconVisibility(tnd, m_visible);
322 if (msg == NIM_ADD || msg == NIM_MODIFY)
323 setIconContents(tnd, m_toolTip, m_hIcon);
324 if (!Shell_NotifyIcon(msg, &tnd))
325 return false;
326 return msg != NIM_ADD || Shell_NotifyIcon(NIM_SETVERSION, &tnd);
327}
328
329// Return the old icon to be freed after modifying the tray icon.
330HICON QWindowsSystemTrayIcon::createIcon(const QIcon &icon)
331{
332 const HICON oldIcon = m_hIcon;
333 m_hIcon = nullptr;
334 if (icon.isNull())
335 return oldIcon;
336 const QSize requestedSize = QSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
337 const QSize size = icon.actualSize(requestedSize);
338 const QPixmap pm = icon.pixmap(size);
339 if (!pm.isNull())
340 m_hIcon = qt_pixmapToWinHICON(pm);
341 return oldIcon;
342}
343
345{
346 *result = 0;
347 switch (message.message) {
348 case MYWM_NOTIFYICON: {
349 Q_ASSERT(q_uNOTIFYICONID == HIWORD(message.lParam));
350 const int trayMessage = LOWORD(message.lParam);
351 switch (trayMessage) {
352 case NIN_SELECT:
353 case NIN_KEYSELECT:
354 if (m_ignoreNextMouseRelease)
355 m_ignoreNextMouseRelease = false;
356 else
358 break;
359 case WM_LBUTTONDBLCLK:
360 m_ignoreNextMouseRelease = true; // Since DBLCLICK Generates a second mouse
361 emit activated(DoubleClick); // release we must ignore it
362 break;
363 case WM_CONTEXTMENU: {
364 // QTBUG-67966: Coordinates may be out of any screen in PROCESS_DPI_UNAWARE mode
365 // since hi-res coordinates are delivered in this case (Windows issue).
366 // Default to primary screen with check to prevent a crash.
367 const QPoint globalPos = QPoint(GET_X_LPARAM(message.wParam), GET_Y_LPARAM(message.wParam));
368 const auto &screenManager = QWindowsContext::instance()->screenManager();
369 const QPlatformScreen *screen = screenManager.screenAtDp(globalPos);
370 if (!screen)
371 screen = screenManager.screens().value(0);
372 if (screen) {
373 emit contextMenuRequested(globalPos, screen);
375 if (m_menu) {
376 // Set the foreground window to the controlling window so that clicking outside
377 // of the menu or window will cause the menu to close
378 SetForegroundWindow(m_hwnd);
379 m_menu->trackPopupMenu(message.hwnd, globalPos.x(), globalPos.y());
380 }
381 }
382 }
383 break;
384 case NIN_BALLOONUSERCLICK:
386 break;
387 case WM_MBUTTONUP:
389 break;
390 default:
391 break;
392 }
393 }
394 break;
395 case WM_INITMENU:
396 case WM_INITMENUPOPUP:
397 QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(message.wParam));
398 break;
399 case WM_CLOSE:
400 QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
401 break;
402 case WM_COMMAND:
404 break;
405 default:
406 if (message.message == MYWM_TASKBARCREATED) {
407 // self-registered message id to handle that
408 // - screen resolution/DPR changed
409 const QIcon oldIcon = m_icon;
410 m_icon = QIcon(); // updateIcon is a no-op if the icon doesn't change
411 updateIcon(oldIcon);
412 // - or tray crashed
413 sendTrayMessage(NIM_ADD);
414 }
415 break;
416 }
417 return false;
418}
419
420#ifndef QT_NO_DEBUG_STREAM
421
423{
424 d << static_cast<const void *>(this) << ", \"" << m_toolTip
425 << "\", hwnd=" << m_hwnd << ", m_hIcon=" << m_hIcon << ", menu="
426 << m_menu.data();
427}
428
430{
431 QDebugStateSaver saver(d);
432 d.nospace();
433 d.noquote();
434 d << "QWindowsSystemTrayIcon(";
435 if (t)
436 t->formatDebug(d);
437 else
438 d << '0';
439 d << ')';
440 return d;
441}
442#endif // !QT_NO_DEBUG_STREAM
443
\inmodule QtCore
\inmodule QtCore
The QIcon class provides scalable icons in different modes and states.
Definition qicon.h:20
bool isNull() const
Returns true if the icon is empty; otherwise returns false.
Definition qicon.cpp:973
qint64 cacheKey() const
Returns a number that identifies the contents of this QIcon object.
Definition qicon.cpp:773
QSize actualSize(const QSize &size, Mode mode=Normal, State state=Off) const
Returns the actual size of the icon for the requested size, mode, and state.
Definition qicon.cpp:880
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
qsizetype size() const noexcept
Definition qlist.h:386
const_reference at(qsizetype i) const noexcept
Definition qlist.h:429
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
QPixmap scaled(int w, int h, Qt::AspectRatioMode aspectMode=Qt::IgnoreAspectRatio, Qt::TransformationMode mode=Qt::FastTransformation) const
Definition qpixmap.h:78
QSize size() const
Returns the size of the pixmap.
Definition qpixmap.cpp:497
bool isNull() const
Returns true if this is a null pixmap; otherwise returns false.
Definition qpixmap.cpp:460
The QPlatformScreen class provides an abstraction for visual displays.
MessageIcon
This enum describes the icon that is shown when a balloon message is displayed.
void contextMenuRequested(QPoint globalPos, const QPlatformScreen *screen)
This signal is emitted when the context menu is requested.
void activated(QPlatformSystemTrayIcon::ActivationReason reason)
This signal is emitted when the user activates the system tray icon.
void messageClicked()
This signal is emitted when the message displayed using showMessage() was clicked by the user.
\inmodule QtCore\reentrant
Definition qpoint.h:23
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:127
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:132
T * data() const
Definition qpointer.h:56
bool isNull() const
Returns true if the referenced object has been destroyed or if there is no referenced object; otherwi...
Definition qpointer.h:67
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtCore
Definition qsize.h:25
constexpr int height() const noexcept
Returns the height.
Definition qsize.h:132
constexpr int width() const noexcept
Returns the width.
Definition qsize.h:129
\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 isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:1083
QString & append(QChar c)
Definition qstring.cpp:3227
Singleton container for all relevant information.
QWindowsScreenManager & screenManager()
static QString classNamePrefix()
static DWORD readAdvancedExplorerSettings(const wchar_t *subKey, DWORD defaultValue)
static QWindowsContext * instance()
static bool notifyAboutToShow(HMENU hmenu)
static bool notifyTriggered(uint id)
bool trackPopupMenu(HWND windowHandle, int x, int y)
Windows native system tray icon.
void cleanup() override
This method is called to cleanup the platform dependent implementation.
bool supportsMessages() const override
Returns true if the system tray supports messages on the platform.
bool winEvent(const MSG &message, long *result)
void formatDebug(QDebug &d) const
QPlatformMenu * createMenu() const override
This method allows platforms to use a different QPlatformMenu for system tray menus than what would n...
void updateToolTip(const QString &tooltip) override
This method is called when the tooltip text did change.
void init() override
This method is called to initialize the platform dependent implementation.
void updateIcon(const QIcon &icon) override
This method is called when the icon did change.
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...
QRect geometry() const override
This method returns the geometry of the platform dependent system tray icon on the screen.
static bool useNativeMenus()
EGLContext ctx
rect
[4]
Combined button and popup list for selecting options.
@ IgnoreAspectRatio
#define Q_UNLIKELY(x)
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition qlogging.h:162
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
GLsizei const GLfloat * v
[13]
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint index
[2]
GLenum GLuint GLenum GLsizei length
GLenum target
GLuint GLsizei const GLchar * message
GLuint entry
GLsizei maxLength
GLdouble GLdouble t
Definition qopenglext.h:243
GLuint in
GLuint64EXT * result
[6]
HICON qt_pixmapToWinHICON(const QPixmap &p)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
QScreen * screen
[1]
Definition main.cpp:29
#define emit
unsigned int uint
Definition qtypes.h:29
struct tagMSG MSG
static QT_BEGIN_NAMESPACE const UINT q_uNOTIFYICONID
#define MYWM_NOTIFYICON
static void setIconVisibility(NOTIFYICONDATA &tnd, bool v)
Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &)
static HWND createTrayIconMessageWindow()
static uint MYWM_TASKBARCREATED
QDebug operator<<(QDebug d, const QWindowsSystemTrayIcon *t)
static void initNotifyIconData(NOTIFYICONDATA &tnd)
static void setIconContents(NOTIFYICONDATA &tnd, const QString &tip, HICON hIcon)
LRESULT QT_WIN_CALLBACK qWindowsTrayIconWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
static void qStringToLimitedWCharArray(QString in, wchar_t *target, int maxLength)
static int indexOfHwnd(HWND hwnd)
const char className[16]
[1]
Definition qwizard.cpp:100
QString title
[35]
QAction * at