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
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
27using namespace Qt::Literals::StringLiterals;
28
29static const UINT q_uNOTIFYICONID = 0;
30
32#define MYWM_NOTIFYICON (WM_APP+101)
33
34Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &);
35
36// Copy QString data to a limited wchar_t array including \0.
37static inline void qStringToLimitedWCharArray(QString in, wchar_t *target, int maxLength)
38{
39 const int length = qMin(maxLength - 1, in.size());
40 if (length < in.size())
41 in.truncate(length);
42 in.toWCharArray(target);
43 target[length] = wchar_t(0);
44}
45
46static inline void initNotifyIconData(NOTIFYICONDATA &tnd)
47{
48 memset(&tnd, 0, sizeof(NOTIFYICONDATA));
49 tnd.cbSize = sizeof(NOTIFYICONDATA);
50 tnd.uVersion = NOTIFYICON_VERSION_4;
51}
52
53static void setIconContents(NOTIFYICONDATA &tnd, const QString &tip, HICON hIcon)
54{
55 tnd.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP;
56 tnd.uCallbackMessage = MYWM_NOTIFYICON;
57 tnd.hIcon = hIcon;
58 qStringToLimitedWCharArray(tip, tnd.szTip, sizeof(tnd.szTip) / sizeof(wchar_t));
59}
60
61static void setIconVisibility(NOTIFYICONDATA &tnd, bool v)
62{
63 tnd.uFlags |= NIF_STATE;
64 tnd.dwStateMask = NIS_HIDDEN;
65 tnd.dwState = v ? 0 : NIS_HIDDEN;
66}
67
68// Match the HWND of the dummy window to the instances
74
75using HwndTrayIconEntries = QList<QWindowsHwndSystemTrayIconEntry>;
76
77Q_GLOBAL_STATIC(HwndTrayIconEntries, hwndTrayIconEntries)
78
79static int indexOfHwnd(HWND hwnd)
80{
81 const HwndTrayIconEntries *entries = hwndTrayIconEntries();
82 for (int i = 0, size = entries->size(); i < size; ++i) {
83 if (entries->at(i).hwnd == hwnd)
84 return i;
85 }
86 return -1;
87}
88
89extern "C" LRESULT QT_WIN_CALLBACK qWindowsTrayIconWndProc(HWND hwnd, UINT message,
90 WPARAM wParam, LPARAM lParam)
91{
93 || message == WM_INITMENU || message == WM_INITMENUPOPUP
94 || message == WM_CLOSE || message == WM_COMMAND) {
95 const int index = indexOfHwnd(hwnd);
96 if (index >= 0) {
97 MSG msg;
98 msg.hwnd = hwnd; // re-create MSG structure
99 msg.message = message; // time and pt fields ignored
100 msg.wParam = wParam;
101 msg.lParam = lParam;
102 msg.pt.x = GET_X_LPARAM(lParam);
103 msg.pt.y = GET_Y_LPARAM(lParam);
104 long result = 0;
105 if (hwndTrayIconEntries()->at(index).trayIcon->winEvent(msg, &result))
106 return result;
107 }
108 }
109 return DefWindowProc(hwnd, message, wParam, lParam);
110}
111
112// Note: Message windows (HWND_MESSAGE) are not sufficient, they
113// will not receive the "TaskbarCreated" message.
114static inline HWND createTrayIconMessageWindow()
115{
117 if (!ctx)
118 return nullptr;
119 // Register window class in the platform plugin.
120 const QString className =
121 ctx->registerWindowClass(QWindowsContext::classNamePrefix() + "TrayIconMessageWindowClass"_L1,
123 const wchar_t windowName[] = L"QTrayIconMessageWindow";
124 return CreateWindowEx(0, reinterpret_cast<const wchar_t *>(className.utf16()),
125 windowName, WS_OVERLAPPED,
126 CW_USEDEFAULT, CW_USEDEFAULT,
127 CW_USEDEFAULT, CW_USEDEFAULT,
128 nullptr, nullptr,
129 static_cast<HINSTANCE>(GetModuleHandle(nullptr)), nullptr);
130}
131
142
144{
145 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this;
146 ensureCleanup();
147}
148
150{
151 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this;
152 m_visible = true;
153 if (!setIconVisible(m_visible))
154 ensureInstalled();
155}
156
158{
159 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this;
160 m_visible = false;
161 ensureCleanup();
162}
163
165{
166 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this;
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 if (!isIconVisible())
188 return QRect();
189
190 NOTIFYICONIDENTIFIER nid;
191 memset(&nid, 0, sizeof(nid));
192 nid.cbSize = sizeof(nid);
193 nid.hWnd = m_hwnd;
194 nid.uID = q_uNOTIFYICONID;
195 RECT rect;
196 const QRect result = SUCCEEDED(Shell_NotifyIconGetRect(&nid, &rect))
197 ? QRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top)
198 : QRect();
199 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "returns" << result;
200 return result;
201}
202
204 const QIcon &icon,
206 int msecsIn)
207{
208 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << title << messageIn << icon
209 << iconType << msecsIn << ')' << this;
210 if (!supportsMessages())
211 return;
212 // For empty messages, ensures that they show when only title is set
213 QString message = messageIn;
214 if (message.isEmpty() && !title.isEmpty())
215 message.append(u' ');
216
217 NOTIFYICONDATA tnd;
219 qStringToLimitedWCharArray(message, tnd.szInfo, 256);
220 qStringToLimitedWCharArray(title, tnd.szInfoTitle, 64);
221
222 tnd.uID = q_uNOTIFYICONID;
223
224 const auto size = icon.actualSize(QSize(256, 256));
225 QPixmap pm = icon.pixmap(size);
226 if (m_hMessageIcon) {
227 DestroyIcon(m_hMessageIcon);
228 m_hMessageIcon = nullptr;
229 }
230 if (pm.isNull()) {
231 tnd.dwInfoFlags = NIIF_INFO;
232 } else {
233 tnd.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
234 m_hMessageIcon = qt_pixmapToWinHICON(pm);
235 tnd.hBalloonIcon = m_hMessageIcon;
236 }
237 tnd.hWnd = m_hwnd;
238 tnd.uTimeout = msecsIn <= 0 ? UINT(10000) : UINT(msecsIn); // 10s default
239 tnd.uFlags = NIF_INFO | NIF_SHOWTIP;
240
241 Shell_NotifyIcon(NIM_MODIFY, &tnd);
242}
243
245{
246 // The key does typically not exist on Windows 10, default to true.
247 return QWindowsContext::readAdvancedExplorerSettings(L"EnableBalloonTips", 1) != 0;
248}
249
251{
252 if (QWindowsTheme::useNativeMenus() && m_menu.isNull())
253 m_menu = new QWindowsPopupMenu;
254 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "returns" << m_menu.data();
255 return m_menu.data();
256}
257
258// Delay-install until an Icon exists
259bool QWindowsSystemTrayIcon::ensureInstalled()
260{
261 if (isInstalled())
262 return true;
263 if (m_hIcon == nullptr)
264 return false;
266 if (Q_UNLIKELY(m_hwnd == nullptr))
267 return false;
268 // For restoring the tray icon after explorer crashes
270 MYWM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
271 // Allow the WM_TASKBARCREATED message through the UIPI filter
272 ChangeWindowMessageFilterEx(m_hwnd, MYWM_TASKBARCREATED, MSGFLT_ALLOW, nullptr);
273 qCDebug(lcQpaTrayIcon) << __FUNCTION__ << this << "MYWM_TASKBARCREATED=" << MYWM_TASKBARCREATED;
274
276 hwndTrayIconEntries()->append(entry);
277 sendTrayMessage(NIM_ADD);
278 return true;
279}
280
281void QWindowsSystemTrayIcon::ensureCleanup()
282{
283 if (isInstalled()) {
284 const int index = indexOfHwnd(m_hwnd);
285 if (index >= 0)
286 hwndTrayIconEntries()->removeAt(index);
287 sendTrayMessage(NIM_DELETE);
288 DestroyWindow(m_hwnd);
289 m_hwnd = nullptr;
290 }
291 if (m_hIcon != nullptr)
292 DestroyIcon(m_hIcon);
293 if (m_hMessageIcon != nullptr)
294 DestroyIcon(m_hMessageIcon);
295 m_hIcon = nullptr;
296 m_hMessageIcon = 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::isIconVisible() const
314{
315 NOTIFYICONIDENTIFIER nid;
316 memset(&nid, 0, sizeof(nid));
317 nid.cbSize = sizeof(nid);
318 nid.hWnd = m_hwnd;
319 nid.uID = q_uNOTIFYICONID;
320 RECT rect;
321 const HRESULT hr = Shell_NotifyIconGetRect(&nid, &rect);
322 // Windows 10 returns S_FALSE if the icon is hidden
323 if (FAILED(hr) || hr == S_FALSE)
324 return false;
325
326 HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
327 MONITORINFO info;
328 info.cbSize = sizeof(MONITORINFO);
329 GetMonitorInfo(monitor, &info);
330 // Windows 11 seems to return a geometry outside of the current monitor's geometry in case of
331 // the icon being hidden. As it's impossible to change the alignment of the task bar on Windows
332 // 11 this check should be fine.
333 return rect.bottom <= info.rcMonitor.bottom;
334}
335
336bool QWindowsSystemTrayIcon::sendTrayMessage(DWORD msg)
337{
338 NOTIFYICONDATA tnd;
340 tnd.uID = q_uNOTIFYICONID;
341 tnd.hWnd = m_hwnd;
342 tnd.uFlags = NIF_SHOWTIP;
343 if (msg != NIM_DELETE && !m_visible)
344 setIconVisibility(tnd, m_visible);
345 if (msg == NIM_ADD || msg == NIM_MODIFY)
346 setIconContents(tnd, m_toolTip, m_hIcon);
347 if (!Shell_NotifyIcon(msg, &tnd))
348 return false;
349 return msg != NIM_ADD || Shell_NotifyIcon(NIM_SETVERSION, &tnd);
350}
351
352// Return the old icon to be freed after modifying the tray icon.
353HICON QWindowsSystemTrayIcon::createIcon(const QIcon &icon)
354{
355 const HICON oldIcon = m_hIcon;
356 m_hIcon = nullptr;
357 if (icon.isNull())
358 return oldIcon;
359 const QSize requestedSize = QSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
360 const QSize size = icon.actualSize(requestedSize);
361 const QPixmap pm = icon.pixmap(size);
362 if (!pm.isNull())
363 m_hIcon = qt_pixmapToWinHICON(pm);
364 return oldIcon;
365}
366
368{
369 *result = 0;
370 switch (message.message) {
371 case MYWM_NOTIFYICON: {
372 Q_ASSERT(q_uNOTIFYICONID == HIWORD(message.lParam));
373 const int trayMessage = LOWORD(message.lParam);
374 switch (trayMessage) {
375 case NIN_SELECT:
376 case NIN_KEYSELECT:
377 if (m_ignoreNextMouseRelease)
378 m_ignoreNextMouseRelease = false;
379 else
381 break;
382 case WM_LBUTTONDBLCLK:
383 m_ignoreNextMouseRelease = true; // Since DBLCLICK Generates a second mouse
384 emit activated(DoubleClick); // release we must ignore it
385 break;
386 case WM_CONTEXTMENU: {
387 // QTBUG-67966: Coordinates may be out of any screen in PROCESS_DPI_UNAWARE mode
388 // since hi-res coordinates are delivered in this case (Windows issue).
389 // Default to primary screen with check to prevent a crash.
390 const QPoint globalPos = QPoint(GET_X_LPARAM(message.wParam), GET_Y_LPARAM(message.wParam));
391 const auto &screenManager = QWindowsContext::instance()->screenManager();
392 const QPlatformScreen *screen = screenManager.screenAtDp(globalPos);
393 if (!screen)
394 screen = screenManager.screens().value(0);
395 if (screen) {
396 emit contextMenuRequested(globalPos, screen);
397 emit activated(Context);
398 if (m_menu) {
399 // Set the foreground window to the controlling window so that clicking outside
400 // of the menu or window will cause the menu to close
401 SetForegroundWindow(m_hwnd);
402 m_menu->trackPopupMenu(message.hwnd, globalPos.x(), globalPos.y());
403 }
404 }
405 }
406 break;
407 case NIN_BALLOONUSERCLICK:
409 break;
410 case WM_MBUTTONUP:
412 break;
413 default:
414 break;
415 }
416 }
417 break;
418 case WM_INITMENU:
419 case WM_INITMENUPOPUP:
420 QWindowsPopupMenu::notifyAboutToShow(reinterpret_cast<HMENU>(message.wParam));
421 break;
422 case WM_CLOSE:
423 QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
424 break;
425 case WM_COMMAND:
427 break;
428 default:
429 if (message.message == MYWM_TASKBARCREATED) {
430 // self-registered message id to handle that
431 // - screen resolution/DPR changed
432 const QIcon oldIcon = m_icon;
433 m_icon = QIcon(); // updateIcon is a no-op if the icon doesn't change
434 updateIcon(oldIcon);
435 // - or tray crashed
436 sendTrayMessage(NIM_ADD);
437 }
438 break;
439 }
440 return false;
441}
442
443#ifndef QT_NO_DEBUG_STREAM
444
446{
447 d << static_cast<const void *>(this) << ", \"" << m_toolTip
448 << "\", hwnd=" << m_hwnd << ", m_hIcon=" << m_hIcon << ", menu="
449 << m_menu.data();
450}
451
453{
454 QDebugStateSaver saver(d);
455 d.nospace();
456 d.noquote();
457 d << "QWindowsSystemTrayIcon(";
458 if (t)
459 t->formatDebug(d);
460 else
461 d << '0';
462 d << ')';
463 return d;
464}
465#endif // !QT_NO_DEBUG_STREAM
466
\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:1019
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:926
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
Returns a copy of the pixmap that is transformed using the given transformation transform and transfo...
Definition qpixmap.h:27
bool isNull() const
Returns true if this is a null pixmap; otherwise returns false.
Definition qpixmap.cpp:456
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:25
constexpr int x() const noexcept
Returns the x coordinate of this point.
Definition qpoint.h:130
constexpr int y() const noexcept
Returns the y coordinate of this point.
Definition qpoint.h:135
T * data() const noexcept
Definition qpointer.h:73
bool isNull() const noexcept
Definition qpointer.h:84
\inmodule QtCore\reentrant
Definition qrect.h:30
\inmodule QtCore
Definition qsize.h:25
\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
QString & append(QChar c)
Definition qstring.cpp:3252
Singleton container for all relevant information.
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.
#define Q_UNLIKELY(x)
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#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
QScreen * screen
[1]
Definition main.cpp:29
#define emit
unsigned int uint
Definition qtypes.h:34
struct tagMSG MSG
long HRESULT
#define MYWM_NOTIFYICON
static void setIconVisibility(NOTIFYICONDATA &tnd, bool v)
Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &)
static HWND createTrayIconMessageWindow()
static const UINT q_uNOTIFYICONID
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)
QList< QWindowsHwndSystemTrayIconEntry > HwndTrayIconEntries
static int indexOfHwnd(HWND hwnd)
const char className[16]
[1]
Definition qwizard.cpp:100
QString title
[35]
QAction * at
QHostInfo info
[0]