Added/fixed some documentation reported by winapi_check.
[wine] / dlls / shell32 / systray.c
1 /*
2  *      Systray
3  *
4  *      Copyright 1999 Kai Morich       <kai.morich@bigfoot.de>
5  *
6  *  Manage the systray window. That it actually appears in the docking
7  *  area of KDE or GNOME is delegated to windows/x11drv/wnd.c,
8  *  X11DRV_WND_DockWindow.
9  *
10  */
11
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include "heap.h"
17 #include "shellapi.h"
18 #include "shell32_main.h"
19 #include "windows.h"
20 #include "commctrl.h"
21 #include "debugtools.h"
22 #include "config.h"
23
24 DEFAULT_DEBUG_CHANNEL(shell)
25
26 typedef struct SystrayData {
27   HWND                  hWnd;
28   HWND                  hWndToolTip;
29   NOTIFYICONDATAA       notifyIcon;
30   int                   nitem; /* number of current element = tooltip id */
31   struct SystrayData    *nextTrayItem;
32 } SystrayData;
33
34 typedef struct Systray {
35   int              hasCritSection;
36   CRITICAL_SECTION critSection;
37   SystrayData      *systrayItemList;
38 } Systray;
39
40 static Systray systray;
41 static int nNumberTrayElements;
42
43
44 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid);
45
46
47 /**************************************************************************
48 *  internal systray
49 *
50 */
51
52 #define SMALL_ICON_SIZE GetSystemMetrics(SM_CXSMICON)
53
54 /* space between icons and frame */
55 #define IBORDER 3
56 #define OBORDER 2
57 #define TBORDER  (OBORDER+1+IBORDER)
58
59 static LRESULT CALLBACK SYSTRAY_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
60 {
61   HDC hdc;
62   PAINTSTRUCT ps;
63
64   switch (message) {
65   case WM_PAINT:
66   {
67     RECT rc;
68     SystrayData  *ptrayItem = systray.systrayItemList;
69
70     while (ptrayItem)
71     {
72       if (ptrayItem->hWnd == hWnd)
73       {
74         hdc = BeginPaint(hWnd, &ps);
75         GetClientRect(hWnd, &rc);
76
77         if (!DrawIconEx(hdc, rc.left, rc.top, ptrayItem->notifyIcon.hIcon,
78                         SMALL_ICON_SIZE, SMALL_ICON_SIZE, 0, 0, DI_DEFAULTSIZE|DI_NORMAL))
79           SYSTRAY_Delete(&ptrayItem->notifyIcon);
80       }
81       ptrayItem = ptrayItem->nextTrayItem;
82     }
83     EndPaint(hWnd, &ps);
84   }
85   break;
86
87   case WM_MOUSEMOVE:
88   case WM_LBUTTONDOWN:
89   case WM_LBUTTONUP:
90   case WM_RBUTTONDOWN:
91   case WM_RBUTTONUP:
92   case WM_MBUTTONDOWN:
93   case WM_MBUTTONUP:
94   {
95     MSG msg;
96     SystrayData *ptrayItem = systray.systrayItemList;
97
98     while ( ptrayItem )
99     {
100       if (ptrayItem->hWnd == hWnd)
101       {
102         msg.hwnd=hWnd;
103         msg.message=message;
104         msg.wParam=wParam;
105         msg.lParam=lParam;
106         msg.time = GetMessageTime ();
107         msg.pt.x = LOWORD(GetMessagePos ());
108         msg.pt.y = HIWORD(GetMessagePos ());
109
110         SendMessageA(ptrayItem->hWndToolTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
111       }
112       ptrayItem = ptrayItem->nextTrayItem;
113     }
114   }
115
116   case WM_LBUTTONDBLCLK:
117   case WM_RBUTTONDBLCLK:
118   case WM_MBUTTONDBLCLK:
119   {
120     int xPos;
121     SystrayData *ptrayItem = systray.systrayItemList;
122
123     while (ptrayItem)
124     {
125       if (ptrayItem->hWnd == hWnd)
126       {
127         xPos = LOWORD(lParam);
128         if( (xPos >= TBORDER) &&
129             (xPos < (TBORDER+SMALL_ICON_SIZE)) )
130         {
131           if (!PostMessageA(ptrayItem->notifyIcon.hWnd, ptrayItem->notifyIcon.uCallbackMessage,
132                             (WPARAM)ptrayItem->notifyIcon.uID, (LPARAM)message))
133             SYSTRAY_Delete(&ptrayItem->notifyIcon);
134           break;
135         }
136       }
137       ptrayItem = ptrayItem->nextTrayItem;
138     }
139   }
140   break;
141
142   default:
143     return (DefWindowProcA(hWnd, message, wParam, lParam));
144   }
145   return (0);
146
147 }
148
149 BOOL SYSTRAY_RegisterClass(void)
150 {
151   WNDCLASSA  wc;
152
153   wc.style         = CS_SAVEBITS;
154   wc.lpfnWndProc   = (WNDPROC)SYSTRAY_WndProc;
155   wc.cbClsExtra    = 0;
156   wc.cbWndExtra    = 0;
157   wc.hInstance     = 0;
158   wc.hIcon         = 0; /* LoadIcon (NULL, IDI_EXCLAMATION); */
159   wc.hCursor       = LoadCursorA(0, IDC_ARROWA);
160   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
161   wc.lpszMenuName  = NULL;
162   wc.lpszClassName = "WineSystray";
163
164   if (!RegisterClassA(&wc)) {
165     ERR("RegisterClass(WineSystray) failed\n");
166     return FALSE;
167   }
168   return TRUE;
169 }
170
171
172 BOOL SYSTRAY_Create(SystrayData *ptrayItem)
173 {
174   RECT rect;
175
176   /* Register the class if this is our first tray item. */
177   if ( nNumberTrayElements == 1 )
178   {
179     if ( !SYSTRAY_RegisterClass() )
180     {
181       ERR( "RegisterClass(WineSystray) failed\n" );
182       return FALSE;
183     }
184   }
185
186   /* Initialize the window size. */
187   rect.left   = 0;
188   rect.top    = 0;
189   rect.right  = SMALL_ICON_SIZE+2*TBORDER;
190   rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
191
192   /* Create tray window for icon. */
193   ptrayItem->hWnd = CreateWindowExA( WS_EX_TRAYWINDOW,
194                                 "WineSystray", "Wine-Systray",
195                                 WS_VISIBLE,
196                                 CW_USEDEFAULT, CW_USEDEFAULT,
197                                 rect.right-rect.left, rect.bottom-rect.top,
198                                 0, 0, 0, 0 );
199   if ( !ptrayItem->hWnd )
200   {
201     ERR( "CreateWindow(WineSystray) failed\n" );
202     return FALSE;
203   }
204
205   /* Create tooltip for icon. */
206   ptrayItem->hWndToolTip = CreateWindowA( TOOLTIPS_CLASSA,NULL,TTS_ALWAYSTIP,
207                                      CW_USEDEFAULT, CW_USEDEFAULT,
208                                      CW_USEDEFAULT, CW_USEDEFAULT,
209                                      ptrayItem->hWnd, 0, 0, 0 );
210   if ( ptrayItem->hWndToolTip==0 )
211   {
212     ERR( "CreateWindow(TOOLTIP) failed\n" );
213     return FALSE;
214   }
215   return TRUE;
216 }
217
218 static void SYSTRAY_RepaintAll(void)
219 {
220   SystrayData  *ptrayItem = systray.systrayItemList;
221
222   while(ptrayItem)
223   {
224     InvalidateRect(ptrayItem->hWnd, NULL, TRUE);
225     ptrayItem = ptrayItem->nextTrayItem;
226   }
227
228 }
229
230 static void SYSTRAY_RepaintItem(int nitem)
231 {
232
233   SystrayData *ptrayItem = systray.systrayItemList;
234
235   while(ptrayItem)
236   {
237     if (ptrayItem->nitem == nitem)
238       InvalidateRect(ptrayItem->hWnd, NULL, TRUE);
239
240     ptrayItem = ptrayItem->nextTrayItem;
241   }
242 }
243
244 void SYSTRAY_InitItem(SystrayData *ptrayItem)
245 {
246   ptrayItem->nitem = nNumberTrayElements++;
247   SYSTRAY_Create(ptrayItem);
248 }
249
250 void SYSTRAY_SetIcon(SystrayData *ptrayItem, HICON hIcon)
251 {
252   ptrayItem->notifyIcon.hIcon = hIcon;
253 }
254
255 void SYSTRAY_SetTip(SystrayData *ptrayItem, CHAR* szTip)
256 {
257   TTTOOLINFOA ti;
258
259   strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip));
260   ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0;
261
262   ti.cbSize = sizeof(TTTOOLINFOA);
263   ti.uFlags = 0;
264   ti.hwnd = ptrayItem->hWnd;
265   ti.hinst = 0;
266   ti.uId = ptrayItem->nitem;
267   ti.lpszText = ptrayItem->notifyIcon.szTip;
268   ti.rect.left   = 0;
269   ti.rect.top    = 0;
270   ti.rect.right  = SMALL_ICON_SIZE+2*TBORDER;
271   ti.rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
272
273   SendMessageA(ptrayItem->hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
274 }
275
276 static void SYSTRAY_ModifyTip(SystrayData *ptrayItem, CHAR* szTip)
277 {
278   TTTOOLINFOA ti;
279
280   strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip));
281   ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0;
282
283   ti.cbSize = sizeof(TTTOOLINFOA);
284   ti.uFlags = 0;
285   ti.hwnd = ptrayItem->hWnd;
286   ti.hinst = 0;
287   ti.uId = ptrayItem->nitem;
288   ti.lpszText = ptrayItem->notifyIcon.szTip;
289   ti.rect.left   = 0;
290   ti.rect.top    = 0;
291   ti.rect.right  = SMALL_ICON_SIZE+2*TBORDER;
292   ti.rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
293
294   SendMessageA(ptrayItem->hWndToolTip, TTM_UPDATETIPTEXTA, 0, (LPARAM)&ti);
295 }
296
297 static void SYSTRAY_TermItem(SystrayData *removeItem)
298 {
299   int nitem;
300   SystrayData **trayItem;
301   TTTOOLINFOA ti;
302   ti.cbSize = sizeof(TTTOOLINFOA);
303   ti.uFlags = 0;
304   ti.hinst = 0;
305
306   /* delete all tooltips ...*/
307   trayItem = &systray.systrayItemList;
308   while (*trayItem) {
309     ti.uId = (*trayItem)->nitem;
310     ti.hwnd = (*trayItem)->hWnd;
311     SendMessageA((*trayItem)->hWndToolTip, TTM_DELTOOLA, 0, (LPARAM)&ti);
312     trayItem = &((*trayItem)->nextTrayItem);
313   }
314   /* ... and add them again, because uID may shift */
315   nitem=0;
316   trayItem = &systray.systrayItemList;
317   while (*trayItem)
318   {
319     if (*trayItem != removeItem)
320     {
321       (*trayItem)->nitem = nitem;
322       ti.uId = nitem;
323       ti.hwnd = (*trayItem)->hWnd;
324       ti.lpszText = (*trayItem)->notifyIcon.szTip;
325       ti.rect.left   = 0;
326       ti.rect.top    = 0;
327       ti.rect.right  = SMALL_ICON_SIZE+2*TBORDER;
328       ti.rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
329       SendMessageA((*trayItem)->hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
330       nitem++;
331     }
332     trayItem = &((*trayItem)->nextTrayItem);
333   }
334   nNumberTrayElements--;
335 }
336
337
338 /**************************************************************************
339 *  helperfunctions
340 *
341 */
342 void SYSTRAY_SetMessage(SystrayData *ptrayItem, UINT uCallbackMessage)
343 {
344   ptrayItem->notifyIcon.uCallbackMessage = uCallbackMessage;
345 }
346
347 static BOOL SYSTRAY_IsEqual(PNOTIFYICONDATAA pnid1, PNOTIFYICONDATAA pnid2)
348 {
349   if (pnid1->hWnd != pnid2->hWnd) return FALSE;
350   if (pnid1->uID  != pnid2->uID)  return FALSE;
351   return TRUE;
352 }
353
354 static BOOL SYSTRAY_Add(PNOTIFYICONDATAA pnid)
355 {
356   SystrayData **ptrayItem = &systray.systrayItemList;
357
358   /* Find empty space for new element. */
359   while( *ptrayItem )
360   {
361     if ( SYSTRAY_IsEqual(pnid, &(*ptrayItem)->notifyIcon) )
362       return FALSE;
363     ptrayItem = &((*ptrayItem)->nextTrayItem);
364   }
365
366   /* Allocate SystrayData for element and zero memory. */
367   (*ptrayItem) = ( SystrayData *)malloc( sizeof(SystrayData) );
368   ZeroMemory( (*ptrayItem), sizeof(SystrayData) );
369
370   /* Copy notification data */
371   memcpy( &(*ptrayItem)->notifyIcon, pnid, sizeof(NOTIFYICONDATAA) );
372
373   /* Initialize and set data for the tray element. */
374   SYSTRAY_InitItem( (*ptrayItem) );
375
376   SYSTRAY_SetIcon   (*ptrayItem, (pnid->uFlags&NIF_ICON)   ?pnid->hIcon           :0);
377   SYSTRAY_SetMessage(*ptrayItem, (pnid->uFlags&NIF_MESSAGE)?pnid->uCallbackMessage:0);
378   SYSTRAY_SetTip    (*ptrayItem, (pnid->uFlags&NIF_TIP)    ?pnid->szTip           :"");
379
380   (*ptrayItem)->nextTrayItem = NULL; /* may be overkill after the ZeroMemory call. */
381
382   /* Repaint all system tray icons as we have added one. */
383   SYSTRAY_RepaintAll();
384
385   TRACE("%p: 0x%08x %d %s\n",  (*ptrayItem), (*ptrayItem)->notifyIcon.hWnd,
386                                (*ptrayItem)->notifyIcon.uID,
387                                (*ptrayItem)->notifyIcon.szTip);
388   return TRUE;
389 }
390
391 static BOOL SYSTRAY_Modify(PNOTIFYICONDATAA pnid)
392 {
393   SystrayData *ptrayItem = systray.systrayItemList;
394
395   while ( ptrayItem )
396   {
397     if ( SYSTRAY_IsEqual(pnid, &ptrayItem->notifyIcon) )
398     {
399       if (pnid->uFlags & NIF_ICON)
400       {
401         SYSTRAY_SetIcon(ptrayItem, pnid->hIcon);
402         SYSTRAY_RepaintItem(ptrayItem->nitem);
403       }
404
405       if (pnid->uFlags & NIF_MESSAGE)
406         SYSTRAY_SetMessage(ptrayItem, pnid->uCallbackMessage);
407
408       if (pnid->uFlags & NIF_TIP)
409         SYSTRAY_ModifyTip(ptrayItem, pnid->szTip);
410
411       TRACE("%p: 0x%08x %d %s\n", ptrayItem, ptrayItem->notifyIcon.hWnd,
412             ptrayItem->notifyIcon.uID, ptrayItem->notifyIcon.szTip);
413       return TRUE;
414     }
415     ptrayItem = ptrayItem->nextTrayItem;
416   }
417   return FALSE; /* not found */
418 }
419
420 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid)
421 {
422   SystrayData **ptrayItem = &systray.systrayItemList;
423
424   while (*ptrayItem)
425   {
426     if (SYSTRAY_IsEqual(pnid, &(*ptrayItem)->notifyIcon))
427     {
428       SystrayData *next = (*ptrayItem)->nextTrayItem;
429       TRACE("%p: 0x%08x %d %s\n", *ptrayItem, (*ptrayItem)->notifyIcon.hWnd,
430             (*ptrayItem)->notifyIcon.uID, (*ptrayItem)->notifyIcon.szTip);
431       SYSTRAY_TermItem(*ptrayItem);
432
433       DestroyWindow((*ptrayItem)->hWndToolTip);
434       DestroyWindow((*ptrayItem)->hWnd);
435
436       free(*ptrayItem);
437       *ptrayItem = next;
438
439       SYSTRAY_RepaintAll();
440
441       return TRUE;
442     }
443     ptrayItem = &((*ptrayItem)->nextTrayItem);
444   }
445
446   return FALSE; /* not found */
447 }
448
449 /*************************************************************************
450  *
451  */
452 BOOL SYSTRAY_Init(void)
453 {
454   if (!systray.hasCritSection)
455   {
456     systray.hasCritSection=1;
457     InitializeCriticalSection(&systray.critSection);
458     MakeCriticalSectionGlobal(&systray.critSection);
459     TRACE(" =%p\n", &systray.critSection);
460   }
461   return TRUE;
462 }
463
464
465 /*************************************************************************
466  * Shell_NotifyIconA                    [SHELL32.297]
467  */
468 BOOL WINAPI Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA pnid )
469 {
470   BOOL flag=FALSE;
471   TRACE("wait %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
472   /* must be serialized because all apps access same systray */
473   EnterCriticalSection(&systray.critSection);
474   TRACE("enter %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
475
476
477   switch(dwMessage) {
478   case NIM_ADD:
479     TRACE("Calling systray add\n");
480     flag = SYSTRAY_Add(pnid);
481     break;
482   case NIM_MODIFY:
483     flag = SYSTRAY_Modify(pnid);
484     break;
485   case NIM_DELETE:
486     flag = SYSTRAY_Delete(pnid);
487     break;
488   }
489
490   LeaveCriticalSection(&systray.critSection);
491   TRACE("leave %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
492   return flag;
493 }
494
495 /*************************************************************************
496  * Shell_NotifyIconW                    [SHELL32.297]
497  */
498 BOOL WINAPI Shell_NotifyIconW (DWORD dwMessage, PNOTIFYICONDATAW pnid )
499 {
500         BOOL ret;
501
502         PNOTIFYICONDATAA p = HeapAlloc(GetProcessHeap(),0,sizeof(NOTIFYICONDATAA));
503         memcpy(p, pnid, sizeof(NOTIFYICONDATAA));
504         if (*(pnid->szTip))
505           lstrcpynWtoA (p->szTip, pnid->szTip, 64 );
506
507         ret = Shell_NotifyIconA(dwMessage, p );
508
509         HeapFree(GetProcessHeap(),0,p);
510         return ret;
511 }
512 /*************************************************************************
513  * Shell_NotifyIcon                     [SHELL32.296]
514  */
515 BOOL WINAPI Shell_NotifyIcon(DWORD dwMessage, PNOTIFYICONDATAA pnid)
516 {
517   return Shell_NotifyIconA(dwMessage, pnid);
518 }