]> git.saurik.com Git - wxWidgets.git/blob - src/msw/taskbar.cpp
Fix wxTaskBarIcon to work under all Windows versions.
[wxWidgets.git] / src / msw / taskbar.cpp
1 /////////////////////////////////////////////////////////////////////////
2 // File: src/msw/taskbar.cpp
3 // Purpose: Implements wxTaskBarIcon class for manipulating icons on
4 // the Windows task bar.
5 // Author: Julian Smart
6 // Modified by: Vaclav Slavik
7 // Created: 24/3/98
8 // RCS-ID: $Id$
9 // Copyright: (c)
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////
12
13 // For compilers that support precompilation, includes "wx.h".
14 #include "wx/wxprec.h"
15
16 #ifdef __BORLANDC__
17 #pragma hdrstop
18 #endif
19
20 #if wxUSE_TASKBARICON
21
22 #ifndef WX_PRECOMP
23 #include "wx/window.h"
24 #include "wx/frame.h"
25 #include "wx/utils.h"
26 #include "wx/menu.h"
27 #endif
28
29 #include "wx/msw/wrapshl.h"
30
31 #include <string.h>
32 #include "wx/taskbar.h"
33 #include "wx/dynlib.h"
34
35 #ifndef NIN_BALLOONTIMEOUT
36 #define NIN_BALLOONTIMEOUT 0x0404
37 #define NIN_BALLOONUSERCLICK 0x0405
38 #endif
39
40 #ifndef NIM_SETVERSION
41 #define NIM_SETVERSION 0x00000004
42 #endif
43
44 #ifndef NIF_INFO
45 #define NIF_INFO 0x00000010
46 #endif
47
48 #ifndef NOTIFYICONDATA_V1_SIZE
49 #ifdef UNICODE
50 #define NOTIFYICONDATA_V1_SIZE 0x0098
51 #else
52 #define NOTIFYICONDATA_V1_SIZE 0x0058
53 #endif
54 #endif
55
56 #ifndef NOTIFYICONDATA_V2_SIZE
57 #ifdef UNICODE
58 #define NOTIFYICONDATA_V2_SIZE 0x03A8;
59 #else
60 #define NOTIFYICONDATA_V2_SIZE 0x01E8;
61 #endif
62 #endif
63
64 // initialized on demand
65 static UINT gs_msgTaskbar = 0;
66 static UINT gs_msgRestartTaskbar = 0;
67
68
69 IMPLEMENT_DYNAMIC_CLASS(wxTaskBarIcon, wxEvtHandler)
70
71 // ============================================================================
72 // implementation
73 // ============================================================================
74
75 // wrapper around Shell_NotifyIcon(): this function is not present in Win95
76 // shell32.dll so load it dynamically to allow programs using wxTaskBarIcon to
77 // start under this OS
78 static BOOL wxShellNotifyIcon(DWORD dwMessage, NOTIFYICONDATA *pData)
79 {
80 #if wxUSE_DYNLIB_CLASS
81 typedef BOOL (WINAPI *Shell_NotifyIcon_t)(DWORD, NOTIFYICONDATA *);
82
83 static Shell_NotifyIcon_t s_pfnShell_NotifyIcon = NULL;
84 static bool s_initialized = false;
85 if ( !s_initialized )
86 {
87 s_initialized = true;
88
89 wxLogNull noLog;
90 wxDynamicLibrary dllShell("shell32.dll");
91 if ( dllShell.IsLoaded() )
92 {
93 wxDL_INIT_FUNC_AW(s_pfn, Shell_NotifyIcon, dllShell);
94 }
95
96 // NB: it's ok to destroy dllShell here, we link to shell32.dll
97 // implicitly so it won't be unloaded
98 }
99
100 return s_pfnShell_NotifyIcon ? (*s_pfnShell_NotifyIcon)(dwMessage, pData)
101 : FALSE;
102 #else // !wxUSE_DYNLIB_CLASS
103 return Shell_NotifyIcon(dwMessage, pData);
104 #endif // wxUSE_DYNLIB_CLASS/!wxUSE_DYNLIB_CLASS
105 }
106
107 // ----------------------------------------------------------------------------
108 // wxTaskBarIconWindow: helper window
109 // ----------------------------------------------------------------------------
110
111 // NB: this class serves two purposes:
112 // 1. win32 needs a HWND associated with taskbar icon, this provides it
113 // 2. we need wxTopLevelWindow so that the app doesn't exit when
114 // last frame is closed but there still is a taskbar icon
115 class wxTaskBarIconWindow : public wxFrame
116 {
117 public:
118 wxTaskBarIconWindow(wxTaskBarIcon *icon)
119 : wxFrame(NULL, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0),
120 m_icon(icon)
121 {
122 }
123
124 WXLRESULT MSWWindowProc(WXUINT msg,
125 WXWPARAM wParam, WXLPARAM lParam)
126 {
127 if (msg == gs_msgRestartTaskbar || msg == gs_msgTaskbar)
128 {
129 return m_icon->WindowProc(msg, wParam, lParam);
130 }
131 else
132 {
133 return wxFrame::MSWWindowProc(msg, wParam, lParam);
134 }
135 }
136
137 private:
138 wxTaskBarIcon *m_icon;
139 };
140
141
142 // ----------------------------------------------------------------------------
143 // NotifyIconData: wrapper around NOTIFYICONDATA
144 // ----------------------------------------------------------------------------
145
146 struct NotifyIconData : public NOTIFYICONDATA
147 {
148 NotifyIconData(WXHWND hwnd)
149 {
150 memset(this, 0, sizeof(NOTIFYICONDATA));
151
152 // Do _not_ use sizeof(NOTIFYICONDATA) here, it may be too big if we're
153 // compiled with newer headers but running on an older system and while
154 // we could do complicated tests for the exact system version it's
155 // easier to just use an old size which should be supported everywhere
156 // from Windows 2000 up and which is all we need as we don't use any
157 // newer features so far. But if we're running under a really ancient
158 // system (Win9x), fall back to even smaller size -- then the balloon
159 // related features won't be available but the rest will still work.
160 cbSize = wxTheApp->GetShell32Version() >= 500
161 ? NOTIFYICONDATA_V2_SIZE
162 : NOTIFYICONDATA_V1_SIZE;
163
164 hWnd = (HWND) hwnd;
165 uCallbackMessage = gs_msgTaskbar;
166 uFlags = NIF_MESSAGE;
167
168 // we use the same id for all taskbar icons as we don't need it to
169 // distinguish between them
170 uID = 99;
171 }
172 };
173
174 // ----------------------------------------------------------------------------
175 // wxTaskBarIcon
176 // ----------------------------------------------------------------------------
177
178 wxTaskBarIcon::wxTaskBarIcon()
179 {
180 m_win = NULL;
181 m_iconAdded = false;
182 RegisterWindowMessages();
183 }
184
185 wxTaskBarIcon::~wxTaskBarIcon()
186 {
187 if ( m_iconAdded )
188 RemoveIcon();
189
190 if ( m_win )
191 {
192 // we must use delete and not Destroy() here because the latter will
193 // only schedule the window to be deleted during the next idle event
194 // processing but we may not get any idle events if there are no other
195 // windows left in the program
196 delete m_win;
197 }
198 }
199
200 // Operations
201 bool wxTaskBarIcon::SetIcon(const wxIcon& icon, const wxString& tooltip)
202 {
203 // NB: we have to create the window lazily because of backward compatibility,
204 // old applications may create a wxTaskBarIcon instance before wxApp
205 // is initialized (as samples/taskbar used to do)
206 if (!m_win)
207 {
208 m_win = new wxTaskBarIconWindow(this);
209 }
210
211 m_icon = icon;
212 m_strTooltip = tooltip;
213
214 NotifyIconData notifyData(GetHwndOf(m_win));
215
216 if (icon.Ok())
217 {
218 notifyData.uFlags |= NIF_ICON;
219 notifyData.hIcon = GetHiconOf(icon);
220 }
221
222 // set NIF_TIP even for an empty tooltip: otherwise it would be impossible
223 // to remove an existing tooltip using this function
224 notifyData.uFlags |= NIF_TIP;
225 if ( !tooltip.empty() )
226 {
227 wxStrlcpy(notifyData.szTip, tooltip.wx_str(), WXSIZEOF(notifyData.szTip));
228 }
229
230 bool ok = wxShellNotifyIcon(m_iconAdded ? NIM_MODIFY
231 : NIM_ADD, &notifyData) != 0;
232
233 if ( !ok )
234 {
235 wxLogLastError(wxT("wxShellNotifyIcon(NIM_MODIFY/ADD)"));
236 }
237
238 if ( !m_iconAdded && ok )
239 m_iconAdded = true;
240
241 return ok;
242 }
243
244 #if wxUSE_TASKBARICON_BALLOONS
245
246 bool
247 wxTaskBarIcon::ShowBalloon(const wxString& title,
248 const wxString& text,
249 unsigned msec,
250 int flags)
251 {
252 wxCHECK_MSG( m_iconAdded, false,
253 wxT("can't be used before the icon is created") );
254
255 const HWND hwnd = GetHwndOf(m_win);
256
257 // we need to enable version 5.0 behaviour to receive notifications about
258 // the balloon disappearance
259 NotifyIconData notifyData(hwnd);
260 notifyData.uFlags = 0;
261 notifyData.uVersion = 3 /* NOTIFYICON_VERSION for Windows 2000/XP */;
262
263 if ( !wxShellNotifyIcon(NIM_SETVERSION, &notifyData) )
264 {
265 wxLogLastError(wxT("wxShellNotifyIcon(NIM_SETVERSION)"));
266 }
267
268 // do show the balloon now
269 notifyData = NotifyIconData(hwnd);
270 notifyData.uFlags |= NIF_INFO;
271 notifyData.uTimeout = msec;
272 wxStrlcpy(notifyData.szInfo, text.wx_str(), WXSIZEOF(notifyData.szInfo));
273 wxStrlcpy(notifyData.szInfoTitle, title.wx_str(),
274 WXSIZEOF(notifyData.szInfoTitle));
275
276 if ( flags & wxICON_INFORMATION )
277 notifyData.dwInfoFlags |= NIIF_INFO;
278 else if ( flags & wxICON_WARNING )
279 notifyData.dwInfoFlags |= NIIF_WARNING;
280 else if ( flags & wxICON_ERROR )
281 notifyData.dwInfoFlags |= NIIF_ERROR;
282
283 bool ok = wxShellNotifyIcon(NIM_MODIFY, &notifyData) != 0;
284 if ( !ok )
285 {
286 wxLogLastError(wxT("wxShellNotifyIcon(NIM_MODIFY)"));
287 }
288
289 return ok;
290 }
291
292 #endif // wxUSE_TASKBARICON_BALLOONS
293
294 bool wxTaskBarIcon::RemoveIcon()
295 {
296 if (!m_iconAdded)
297 return false;
298
299 m_iconAdded = false;
300
301 NotifyIconData notifyData(GetHwndOf(m_win));
302
303 bool ok = wxShellNotifyIcon(NIM_DELETE, &notifyData) != 0;
304 if ( !ok )
305 {
306 wxLogLastError(wxT("wxShellNotifyIcon(NIM_DELETE)"));
307 }
308
309 return ok;
310 }
311
312 #if wxUSE_MENUS
313 bool wxTaskBarIcon::PopupMenu(wxMenu *menu)
314 {
315 wxASSERT_MSG( m_win != NULL, wxT("taskbar icon not initialized") );
316
317 static bool s_inPopup = false;
318
319 if (s_inPopup)
320 return false;
321
322 s_inPopup = true;
323
324 int x, y;
325 wxGetMousePosition(&x, &y);
326
327 m_win->Move(x, y);
328
329 m_win->PushEventHandler(this);
330
331 menu->UpdateUI();
332
333 // the SetForegroundWindow() and PostMessage() calls are needed to work
334 // around Win32 bug with the popup menus shown for the notifications as
335 // documented at http://support.microsoft.com/kb/q135788/
336 ::SetForegroundWindow(GetHwndOf(m_win));
337
338 bool rval = m_win->PopupMenu(menu, 0, 0);
339
340 ::PostMessage(GetHwndOf(m_win), WM_NULL, 0, 0L);
341
342 m_win->PopEventHandler(false);
343
344 s_inPopup = false;
345
346 return rval;
347 }
348 #endif // wxUSE_MENUS
349
350 void wxTaskBarIcon::RegisterWindowMessages()
351 {
352 static bool s_registered = false;
353
354 if ( !s_registered )
355 {
356 // Taskbar restart msg will be sent to us if the icon needs to be redrawn
357 gs_msgRestartTaskbar = RegisterWindowMessage(wxT("TaskbarCreated"));
358
359 // Also register the taskbar message here
360 gs_msgTaskbar = ::RegisterWindowMessage(wxT("wxTaskBarIconMessage"));
361
362 s_registered = true;
363 }
364 }
365
366 // ----------------------------------------------------------------------------
367 // wxTaskBarIcon window proc
368 // ----------------------------------------------------------------------------
369
370 long wxTaskBarIcon::WindowProc(unsigned int msg,
371 unsigned int WXUNUSED(wParam),
372 long lParam)
373 {
374 if ( msg == gs_msgRestartTaskbar ) // does the icon need to be redrawn?
375 {
376 m_iconAdded = false;
377 SetIcon(m_icon, m_strTooltip);
378 return 0;
379 }
380
381 // this function should only be called for gs_msg(Restart)Taskbar messages
382 wxASSERT( msg == gs_msgTaskbar );
383
384 wxEventType eventType = 0;
385 switch ( lParam )
386 {
387 case WM_LBUTTONDOWN:
388 eventType = wxEVT_TASKBAR_LEFT_DOWN;
389 break;
390
391 case WM_LBUTTONUP:
392 eventType = wxEVT_TASKBAR_LEFT_UP;
393 break;
394
395 case WM_RBUTTONDOWN:
396 eventType = wxEVT_TASKBAR_RIGHT_DOWN;
397 break;
398
399 case WM_RBUTTONUP:
400 eventType = wxEVT_TASKBAR_RIGHT_UP;
401 break;
402
403 case WM_LBUTTONDBLCLK:
404 eventType = wxEVT_TASKBAR_LEFT_DCLICK;
405 break;
406
407 case WM_RBUTTONDBLCLK:
408 eventType = wxEVT_TASKBAR_RIGHT_DCLICK;
409 break;
410
411 case WM_MOUSEMOVE:
412 eventType = wxEVT_TASKBAR_MOVE;
413 break;
414
415 case NIN_BALLOONTIMEOUT:
416 eventType = wxEVT_TASKBAR_BALLOON_TIMEOUT;
417 break;
418
419 case NIN_BALLOONUSERCLICK:
420 eventType = wxEVT_TASKBAR_BALLOON_CLICK;
421 break;
422 }
423
424 if ( eventType )
425 {
426 wxTaskBarIconEvent event(eventType, this);
427
428 ProcessEvent(event);
429 }
430
431 return 0;
432 }
433
434 #endif // wxUSE_TASKBARICON
435