Qt 4.8
qsystemtrayicon_win.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qsystemtrayicon_p.h"
43 #ifndef QT_NO_SYSTEMTRAYICON
44 
45 #ifndef _WIN32_WINNT
46 #define _WIN32_WINNT 0x0600
47 #endif
48 
49 #ifndef _WIN32_IE
50 #define _WIN32_IE 0x600
51 #endif
52 
53 #include <qt_windows.h>
54 #include <windowsx.h>
55 #include <commctrl.h>
56 
57 #include <private/qsystemlibrary_p.h>
58 #include <QApplication>
59 #include <QSettings>
60 
62 
63 static const UINT q_uNOTIFYICONID = 0;
64 
66 #define MYWM_NOTIFYICON (WM_APP+101)
67 
69  DWORD cbSize;
70  HWND hWnd;
71  UINT uID;
73 };
74 
75 #ifndef NOTIFYICON_VERSION_4
76 #define NOTIFYICON_VERSION_4 4
77 #endif
78 
79 #ifndef NIN_SELECT
80 #define NIN_SELECT (WM_USER + 0)
81 #endif
82 
83 #ifndef NIN_KEYSELECT
84 #define NIN_KEYSELECT (WM_USER + 1)
85 #endif
86 
87 #ifndef NIN_BALLOONTIMEOUT
88 #define NIN_BALLOONTIMEOUT (WM_USER + 4)
89 #endif
90 
91 #ifndef NIN_BALLOONUSERCLICK
92 #define NIN_BALLOONUSERCLICK (WM_USER + 5)
93 #endif
94 
95 #ifndef NIF_SHOWTIP
96 #define NIF_SHOWTIP 0x00000080
97 #endif
98 
99 #define Q_MSGFLT_ALLOW 1
100 
101 typedef HRESULT (WINAPI *PtrShell_NotifyIconGetRect)(const Q_NOTIFYICONIDENTIFIER* identifier, RECT* iconLocation);
102 typedef BOOL (WINAPI *PtrChangeWindowMessageFilter)(UINT message, DWORD dwFlag);
103 typedef BOOL (WINAPI *PtrChangeWindowMessageFilterEx)(HWND hWnd, UINT message, DWORD action, void* pChangeFilterStruct);
104 
106 {
107 public:
110  bool winEvent( MSG *m, long *result );
111  bool trayMessage(DWORD msg);
112  void setIconContents(NOTIFYICONDATA &data);
113  bool showMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs);
114  QRect findIconGeometry(const int a_iButtonID);
115  void createIcon();
116  HICON hIcon;
118  QSystemTrayIcon *q;
119 private:
122  int version;
124 };
125 
126 static bool allowsMessages()
127 {
128 #ifndef QT_NO_SETTINGS
129  QSettings settings(QLatin1String("HKEY_CURRENT_USER\\Software\\Microsoft"
130  "\\Windows\\CurrentVersion\\Explorer\\Advanced"), QSettings::NativeFormat);
131  return settings.value(QLatin1String("EnableBalloonTips"), true).toBool();
132 #else
133  return false;
134 #endif
135 }
136 
138  : hIcon(0), q(object), ignoreNextMouseRelease(false)
139 
140 {
142  notifyIconSize = sizeof(NOTIFYICONDATA);
144  } else {
145  notifyIconSize = NOTIFYICONDATA_V2_SIZE;
146  version = NOTIFYICON_VERSION;
147  }
148 
149  maxTipLength = 128;
150 
151  // For restoring the tray icon after explorer crashes
152  if (!MYWM_TASKBARCREATED) {
153  MYWM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
154  }
155 
156  // Allow the WM_TASKBARCREATED message through the UIPI filter on Windows Vista and higher
157  static PtrChangeWindowMessageFilterEx pChangeWindowMessageFilterEx =
158  (PtrChangeWindowMessageFilterEx)QSystemLibrary::resolve(QLatin1String("user32"), "ChangeWindowMessageFilterEx");
159 
160  if (pChangeWindowMessageFilterEx) {
161  // Call the safer ChangeWindowMessageFilterEx API if available
162  pChangeWindowMessageFilterEx(winId(), MYWM_TASKBARCREATED, Q_MSGFLT_ALLOW, 0);
163  } else {
164  static PtrChangeWindowMessageFilter pChangeWindowMessageFilter =
165  (PtrChangeWindowMessageFilter)QSystemLibrary::resolve(QLatin1String("user32"), "ChangeWindowMessageFilter");
166 
167  if (pChangeWindowMessageFilter) {
168  // Call the deprecated ChangeWindowMessageFilter API otherwise
169  pChangeWindowMessageFilter(MYWM_TASKBARCREATED, Q_MSGFLT_ALLOW);
170  }
171  }
172 }
173 
175 {
176  if (hIcon)
177  DestroyIcon(hIcon);
178 }
179 
180 void QSystemTrayIconSys::setIconContents(NOTIFYICONDATA &tnd)
181 {
182  tnd.uFlags |= NIF_MESSAGE | NIF_ICON | NIF_TIP;
183  tnd.uCallbackMessage = MYWM_NOTIFYICON;
184  tnd.hIcon = hIcon;
185  QString tip = q->toolTip();
186 
187  if (!tip.isNull()) {
188  tip = tip.left(maxTipLength - 1) + QChar();
189  memcpy(tnd.szTip, tip.utf16(), qMin(tip.length() + 1, maxTipLength) * sizeof(wchar_t));
190  }
191 }
192 
194 {
195  switch (icon) {
197  return NIIF_INFO;
199  return NIIF_WARNING;
201  return NIIF_ERROR;
203  return NIIF_NONE;
204  default:
205  Q_ASSERT_X(false, "QSystemTrayIconSys::showMessage", "Invalid QSystemTrayIcon::MessageIcon value");
206  return NIIF_NONE;
207  }
208 }
209 
211 {
212  NOTIFYICONDATA tnd;
213  memset(&tnd, 0, notifyIconSize);
214 
215  memcpy(tnd.szInfo, message.utf16(), qMin(message.length() + 1, 256) * sizeof(wchar_t));
216  memcpy(tnd.szInfoTitle, title.utf16(), qMin(title.length() + 1, 64) * sizeof(wchar_t));
217 
218  tnd.uID = q_uNOTIFYICONID;
219  tnd.dwInfoFlags = iconFlag(type);
220  tnd.cbSize = notifyIconSize;
221  tnd.hWnd = winId();
222  tnd.uTimeout = uSecs;
223  tnd.uFlags = NIF_INFO | NIF_SHOWTIP;
224 
226 
227  return Shell_NotifyIcon(NIM_MODIFY, &tnd);
228 }
229 
231 {
232  NOTIFYICONDATA tnd;
233  memset(&tnd, 0, notifyIconSize);
234 
235  tnd.uID = q_uNOTIFYICONID;
236  tnd.cbSize = notifyIconSize;
237  tnd.hWnd = winId();
238  tnd.uFlags = NIF_SHOWTIP;
239  tnd.uVersion = version;
240 
242 
243  if (msg == NIM_ADD || msg == NIM_MODIFY) {
244  setIconContents(tnd);
245  }
246 
247  bool success = Shell_NotifyIcon(msg, &tnd);
248 
249  if (msg == NIM_ADD)
250  return success && Shell_NotifyIcon(NIM_SETVERSION, &tnd);
251  else
252  return success;
253 }
254 
256 {
257  hIcon = 0;
258  QIcon icon = q->icon();
259  if (icon.isNull())
260  return;
261 
262  const int iconSizeX = GetSystemMetrics(SM_CXSMICON);
263  const int iconSizeY = GetSystemMetrics(SM_CYSMICON);
264  QSize size = icon.actualSize(QSize(iconSizeX, iconSizeY));
265  QPixmap pm = icon.pixmap(size);
266  if (pm.isNull())
267  return;
268 
269  hIcon = pm.toWinHICON();
270 }
271 
272 bool QSystemTrayIconSys::winEvent( MSG *m, long *result )
273 {
274  switch(m->message) {
275  case MYWM_NOTIFYICON:
276  {
277  int message = 0;
278  QPoint gpos;
279 
280  if (version == NOTIFYICON_VERSION_4) {
281  Q_ASSERT(q_uNOTIFYICONID == HIWORD(m->lParam));
282  message = LOWORD(m->lParam);
283  gpos = QPoint(GET_X_LPARAM(m->wParam), GET_Y_LPARAM(m->wParam));
284  } else {
285  Q_ASSERT(q_uNOTIFYICONID == m->wParam);
286  message = m->lParam;
287  gpos = QCursor::pos();
288  }
289 
290  switch (message) {
291  case NIN_SELECT:
292  case NIN_KEYSELECT:
294  ignoreNextMouseRelease = false;
295  else
297  break;
298 
299  case WM_LBUTTONDBLCLK:
300  ignoreNextMouseRelease = true; // Since DBLCLICK Generates a second mouse
301  // release we must ignore it
303  break;
304 
305  case WM_CONTEXTMENU:
306  if (q->contextMenu()) {
307  q->contextMenu()->popup(gpos);
309  }
311  break;
312 
314  emit q->messageClicked();
315  break;
316 
317  case WM_MBUTTONUP:
319  break;
320 
321  default:
322  break;
323  }
324  break;
325  }
326  default:
327  if (m->message == MYWM_TASKBARCREATED)
328  trayMessage(NIM_ADD);
329  else
330  return QWidget::winEvent(m, result);
331  break;
332  }
333  return 0;
334 }
335 
337 {
339  if (!sys) {
340  sys = new QSystemTrayIconSys(q);
341  sys->createIcon();
342  sys->trayMessage(NIM_ADD);
343  }
344 }
345 
346 /*
347 * This function tries to determine the icon geometry from the tray
348 *
349 * If it fails an invalid rect is returned.
350 */
352 {
353  static PtrShell_NotifyIconGetRect Shell_NotifyIconGetRect =
354  (PtrShell_NotifyIconGetRect)QSystemLibrary::resolve(QLatin1String("shell32"), "Shell_NotifyIconGetRect");
355 
356  if (Shell_NotifyIconGetRect) {
358  memset(&nid, 0, sizeof(nid));
359  nid.cbSize = sizeof(nid);
360  nid.hWnd = winId();
361  nid.uID = iconId;
362 
363  RECT rect;
364  HRESULT hr = Shell_NotifyIconGetRect(&nid, &rect);
365  if (SUCCEEDED(hr)) {
366  return QRect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
367  }
368  }
369 
370  QRect ret;
371 
372  TBBUTTON buttonData;
373  DWORD processID = 0;
374  HWND trayHandle = FindWindow(L"Shell_TrayWnd", NULL);
375 
376  //find the toolbar used in the notification area
377  if (trayHandle) {
378  trayHandle = FindWindowEx(trayHandle, NULL, L"TrayNotifyWnd", NULL);
379  if (trayHandle) {
380  HWND hwnd = FindWindowEx(trayHandle, NULL, L"SysPager", NULL);
381  if (hwnd) {
382  hwnd = FindWindowEx(hwnd, NULL, L"ToolbarWindow32", NULL);
383  if (hwnd)
384  trayHandle = hwnd;
385  }
386  }
387  }
388 
389  if (!trayHandle)
390  return ret;
391 
392  GetWindowThreadProcessId(trayHandle, &processID);
393  if (processID <= 0)
394  return ret;
395 
396  HANDLE trayProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ, 0, processID);
397  if (!trayProcess)
398  return ret;
399 
400  int buttonCount = SendMessage(trayHandle, TB_BUTTONCOUNT, 0, 0);
401  LPVOID data = VirtualAllocEx(trayProcess, NULL, sizeof(TBBUTTON), MEM_COMMIT, PAGE_READWRITE);
402 
403  if ( buttonCount < 1 || !data ) {
404  CloseHandle(trayProcess);
405  return ret;
406  }
407 
408  //search for our icon among all toolbar buttons
409  for (int toolbarButton = 0; toolbarButton < buttonCount; ++toolbarButton ) {
410  SIZE_T numBytes = 0;
411  DWORD appData[2] = { 0, 0 };
412  SendMessage(trayHandle, TB_GETBUTTON, toolbarButton , (LPARAM)data);
413 
414  if (!ReadProcessMemory(trayProcess, data, &buttonData, sizeof(TBBUTTON), &numBytes))
415  continue;
416 
417  if (!ReadProcessMemory(trayProcess, (LPVOID) buttonData.dwData, appData, sizeof(appData), &numBytes))
418  continue;
419 
420  int currentIconId = appData[1];
421  HWND currentIconHandle = (HWND) appData[0];
422  bool isHidden = buttonData.fsState & TBSTATE_HIDDEN;
423 
424  if (currentIconHandle == winId() &&
425  currentIconId == iconId && !isHidden) {
426  SendMessage(trayHandle, TB_GETITEMRECT, toolbarButton , (LPARAM)data);
427  RECT iconRect = {0, 0, 0, 0};
428  if(ReadProcessMemory(trayProcess, data, &iconRect, sizeof(RECT), &numBytes)) {
429  MapWindowPoints(trayHandle, NULL, (LPPOINT)&iconRect, 2);
430  QRect geometry(iconRect.left + 1, iconRect.top + 1,
431  iconRect.right - iconRect.left - 2,
432  iconRect.bottom - iconRect.top - 2);
433  if (geometry.isValid())
434  ret = geometry;
435  break;
436  }
437  }
438  }
439  VirtualFreeEx(trayProcess, data, 0, MEM_RELEASE);
440  CloseHandle(trayProcess);
441  return ret;
442 }
443 
444 void QSystemTrayIconPrivate::showMessage_sys(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, int timeOut)
445 {
446  if (!sys || !allowsMessages())
447  return;
448 
449  uint uSecs = 0;
450  if ( timeOut < 0)
451  uSecs = 10000; //10 sec default
452  else uSecs = (int)timeOut;
453 
454  //message is limited to 255 chars + NULL
455  QString messageString;
456  if (message.isEmpty() && !title.isEmpty())
457  messageString = QLatin1Char(' '); //ensures that the message shows when only title is set
458  else
459  messageString = message.left(255) + QChar();
460 
461  //title is limited to 63 chars + NULL
462  QString titleString = title.left(63) + QChar();
463 
464  sys->showMessage(titleString, messageString, type, uSecs);
465 }
466 
468 {
469  if (!sys)
470  return QRect();
471 
472  return sys->findIconGeometry(q_uNOTIFYICONID);
473 }
474 
476 {
477  if (!sys)
478  return;
479 
480  sys->trayMessage(NIM_DELETE);
481  delete sys;
482  sys = 0;
483 }
484 
486 {
487  if (!sys)
488  return;
489 
490  HICON hIconToDestroy = sys->hIcon;
491 
492  sys->createIcon();
493  sys->trayMessage(NIM_MODIFY);
494 
495  if (hIconToDestroy)
496  DestroyIcon(hIconToDestroy);
497 }
498 
500 {
501 
502 }
503 
505 {
506  if (!sys)
507  return;
508 
509  sys->trayMessage(NIM_MODIFY);
510 }
511 
513 {
514  return true;
515 }
516 
518 {
519  return allowsMessages();
520 }
521 
523 
524 #endif
#define NIN_KEYSELECT
void showMessage_sys(const QString &msg, const QString &title, QSystemTrayIcon::MessageIcon icon, int secs)
int type
Definition: qmetatype.cpp:239
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Returns the value for setting key.
Definition: qsettings.cpp:3460
Q_DECL_CONSTEXPR const T & qMin(const T &a, const T &b)
Definition: qglobal.h:1215
#define QT_END_NAMESPACE
This macro expands to.
Definition: qglobal.h:90
QSize size() const
Definition: quuid.h:52
BOOL(WINAPI * PtrChangeWindowMessageFilter)(UINT message, DWORD dwFlag)
void popup(const QPoint &pos, QAction *at=0)
Displays the menu so that the action atAction will be at the specified global position p...
Definition: qmenu.cpp:1847
The QSettings class provides persistent platform-independent application settings.
Definition: qsettings.h:73
#define NIF_SHOWTIP
int length() const
Returns the number of characters in this string.
Definition: qstring.h:696
#define NIN_BALLOONUSERCLICK
The QWidget class is the base class of all user interface objects.
Definition: qwidget.h:150
static WinVersion windowsVersion()
Returns the version of the Windows operating system on which the application is run (Windows only)...
QLatin1String(DBUS_INTERFACE_DBUS))) Q_GLOBAL_STATIC_WITH_ARGS(QString
bool toBool() const
Returns the variant as a bool if the variant has type() Bool.
Definition: qvariant.cpp:2691
static bool allowsMessages()
bool winEvent(MSG *m, long *result)
This special event handler can be reimplemented in a subclass to receive native Windows events which ...
QSystemTrayIcon * q
The QString class provides a Unicode character string.
Definition: qstring.h:83
#define Q_ASSERT(cond)
Definition: qglobal.h:1823
The QChar class provides a 16-bit Unicode character.
Definition: qchar.h:72
void activated(QSystemTrayIcon::ActivationReason reason)
This signal is emitted when the user activates the system tray icon.
#define Q_Q(Class)
Definition: qglobal.h:2483
bool isHidden() const
Returns true if the widget is hidden, otherwise returns false.
Definition: qwidget.h:1008
#define NIN_SELECT
#define QT_BEGIN_NAMESPACE
This macro expands to.
Definition: qglobal.h:89
QString toolTip
the tooltip for the system tray entry
QString left(int n) const Q_REQUIRED_RESULT
Returns a substring that contains the n leftmost characters of the string.
Definition: qstring.cpp:3664
bool testAttribute(Qt::WidgetAttribute) const
Returns true if attribute attribute is set on this widget; otherwise returns false.
Definition: qwidget.h:1041
bool isEmpty() const
Returns true if the string has no characters; otherwise returns false.
Definition: qstring.h:704
static bool isSystemTrayAvailable_sys()
#define emit
Definition: qobjectdefs.h:76
bool isNull() const
Returns true if the icon is empty; otherwise returns false.
Definition: qicon.cpp:769
QWidgetData * data
Definition: qwidget.h:815
QRect findIconGeometry(const int a_iButtonID)
#define GET_X_LPARAM(lp)
void setIconContents(NOTIFYICONDATA &data)
static const char * data(const QByteArray &arr)
unsigned int uint
Definition: qglobal.h:996
QMenu * contextMenu() const
Returns the current context menu for the system tray entry.
static uint MYWM_TASKBARCREATED
void * HANDLE
Definition: qnamespace.h:1671
QIcon icon
the system tray icon
QRect rect() const
bool showMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon type, uint uSecs)
bool isNull() const
Returns true if this string is null; otherwise returns false.
Definition: qstring.h:505
struct tagMSG MSG
long HRESULT
#define Q_ASSERT_X(cond, where, what)
Definition: qglobal.h:1837
void * resolve(const char *symbol)
virtual bool winEvent(MSG *message, long *result)
This special event handler can be reimplemented in a subclass to receive native Windows events which ...
Definition: qwidget.cpp:9941
HICON toWinHICON() const
Returns the HICON handle.
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:730
The QPoint class defines a point in the plane using integer precision.
Definition: qpoint.h:53
static const UINT q_uNOTIFYICONID
The QRect class defines a rectangle in the plane using integer precision.
Definition: qrect.h:58
static int iconFlag(QSystemTrayIcon::MessageIcon icon)
#define Q_MSGFLT_ALLOW
void activateWindow()
Sets the top-level widget containing this widget to be the active window.
The QPixmap class is an off-screen image representation that can be used as a paint device...
Definition: qpixmap.h:71
bool trayMessage(DWORD msg)
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:693
#define MYWM_NOTIFYICON
The QSize class defines the size of a two-dimensional object using integer point precision.
Definition: qsize.h:53
#define NOTIFYICON_VERSION_4
WId winId() const
Returns the window system identifier of the widget.
Definition: qwidget.cpp:2557
#define GET_Y_LPARAM(lp)
HRESULT(WINAPI * PtrShell_NotifyIconGetRect)(const Q_NOTIFYICONIDENTIFIER *identifier, RECT *iconLocation)
bool isNull() const
Returns true if this is a null pixmap; otherwise returns false.
Definition: qpixmap.cpp:615
MessageIcon
This enum describes the icon that is shown when a balloon message is displayed.
bool isValid() const
Returns true if the rectangle is valid, otherwise returns false.
Definition: qrect.h:237
const QRect & geometry() const
QSystemTrayIconSys(QSystemTrayIcon *icon, QSystemTrayIconPrivate *d)
The QLatin1Char class provides an 8-bit ASCII/Latin-1 character.
Definition: qchar.h:55
BOOL(WINAPI * PtrChangeWindowMessageFilterEx)(HWND hWnd, UINT message, DWORD action, void *pChangeFilterStruct)
static QPoint pos()
Returns the position of the cursor (hot spot) in global screen coordinates.
Definition: qcursor_mac.mm:310
The QSystemTrayIcon class provides an icon for an application in the system tray. ...
const ushort * utf16() const
Returns the QString as a &#39;\0\&#39;-terminated array of unsigned shorts.
Definition: qstring.cpp:5290
void messageClicked()
This signal is emitted when the message displayed using showMessage() was clicked by the user...
The QIcon class provides scalable icons in different modes and states.
Definition: qicon.h:60