Merged msacm and msacm32 dlls.
[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   /* keep our own copy of "hIcon" object */     
253   ptrayItem->notifyIcon.hIcon = CopyIcon(hIcon); 
254 }
255
256 void SYSTRAY_SetTip(SystrayData *ptrayItem, CHAR* szTip)
257 {
258   TTTOOLINFOA ti;
259
260   strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip));
261   ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0;
262
263   ti.cbSize = sizeof(TTTOOLINFOA);
264   ti.uFlags = 0;
265   ti.hwnd = ptrayItem->hWnd;
266   ti.hinst = 0;
267   ti.uId = ptrayItem->nitem;
268   ti.lpszText = ptrayItem->notifyIcon.szTip;
269   ti.rect.left   = 0;
270   ti.rect.top    = 0;
271   ti.rect.right  = SMALL_ICON_SIZE+2*TBORDER;
272   ti.rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
273
274   SendMessageA(ptrayItem->hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
275 }
276
277 static void SYSTRAY_ModifyTip(SystrayData *ptrayItem, CHAR* szTip)
278 {
279   TTTOOLINFOA ti;
280
281   strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip));
282   ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0;
283
284   ti.cbSize = sizeof(TTTOOLINFOA);
285   ti.uFlags = 0;
286   ti.hwnd = ptrayItem->hWnd;
287   ti.hinst = 0;
288   ti.uId = ptrayItem->nitem;
289   ti.lpszText = ptrayItem->notifyIcon.szTip;
290   ti.rect.left   = 0;
291   ti.rect.top    = 0;
292   ti.rect.right  = SMALL_ICON_SIZE+2*TBORDER;
293   ti.rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
294
295   SendMessageA(ptrayItem->hWndToolTip, TTM_UPDATETIPTEXTA, 0, (LPARAM)&ti);
296 }
297
298 static void SYSTRAY_TermItem(SystrayData *removeItem)
299 {
300   int nitem;
301   SystrayData **trayItem;
302   TTTOOLINFOA ti;
303   ti.cbSize = sizeof(TTTOOLINFOA);
304   ti.uFlags = 0;
305   ti.hinst = 0;
306
307   /* delete all tooltips ...*/
308   trayItem = &systray.systrayItemList;
309   while (*trayItem) {
310     ti.uId = (*trayItem)->nitem;
311     ti.hwnd = (*trayItem)->hWnd;
312     SendMessageA((*trayItem)->hWndToolTip, TTM_DELTOOLA, 0, (LPARAM)&ti);
313     trayItem = &((*trayItem)->nextTrayItem);
314   }
315   /* ... and add them again, because uID may shift */
316   nitem=0;
317   trayItem = &systray.systrayItemList;
318   while (*trayItem)
319   {
320     if (*trayItem != removeItem)
321     {
322       (*trayItem)->nitem = nitem;
323       ti.uId = nitem;
324       ti.hwnd = (*trayItem)->hWnd;
325       ti.lpszText = (*trayItem)->notifyIcon.szTip;
326       ti.rect.left   = 0;
327       ti.rect.top    = 0;
328       ti.rect.right  = SMALL_ICON_SIZE+2*TBORDER;
329       ti.rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
330       SendMessageA((*trayItem)->hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
331       nitem++;
332     }
333     trayItem = &((*trayItem)->nextTrayItem);
334   }
335   nNumberTrayElements--;
336 }
337
338
339 /**************************************************************************
340 *  helperfunctions
341 *
342 */
343 void SYSTRAY_SetMessage(SystrayData *ptrayItem, UINT uCallbackMessage)
344 {
345   ptrayItem->notifyIcon.uCallbackMessage = uCallbackMessage;
346 }
347
348 static BOOL SYSTRAY_IsEqual(PNOTIFYICONDATAA pnid1, PNOTIFYICONDATAA pnid2)
349 {
350   if (pnid1->hWnd != pnid2->hWnd) return FALSE;
351   if (pnid1->uID  != pnid2->uID)  return FALSE;
352   return TRUE;
353 }
354
355 static BOOL SYSTRAY_Add(PNOTIFYICONDATAA pnid)
356 {
357   SystrayData **ptrayItem = &systray.systrayItemList;
358
359   /* Find empty space for new element. */
360   while( *ptrayItem )
361   {
362     if ( SYSTRAY_IsEqual(pnid, &(*ptrayItem)->notifyIcon) )
363       return FALSE;
364     ptrayItem = &((*ptrayItem)->nextTrayItem);
365   }
366
367   /* Allocate SystrayData for element and zero memory. */
368   (*ptrayItem) = ( SystrayData *)malloc( sizeof(SystrayData) );
369   ZeroMemory( (*ptrayItem), sizeof(SystrayData) );
370
371   /* Copy notification data */
372   memcpy( &(*ptrayItem)->notifyIcon, pnid, sizeof(NOTIFYICONDATAA) );
373
374   /* Initialize and set data for the tray element. */
375   SYSTRAY_InitItem( (*ptrayItem) );
376
377   SYSTRAY_SetIcon   (*ptrayItem, (pnid->uFlags&NIF_ICON)   ?pnid->hIcon           :0);
378   SYSTRAY_SetMessage(*ptrayItem, (pnid->uFlags&NIF_MESSAGE)?pnid->uCallbackMessage:0);
379   SYSTRAY_SetTip    (*ptrayItem, (pnid->uFlags&NIF_TIP)    ?pnid->szTip           :"");
380
381   (*ptrayItem)->nextTrayItem = NULL; /* may be overkill after the ZeroMemory call. */
382
383   /* Repaint all system tray icons as we have added one. */
384   SYSTRAY_RepaintAll();
385
386   TRACE("%p: 0x%08x %d %s\n",  (*ptrayItem), (*ptrayItem)->notifyIcon.hWnd,
387                                (*ptrayItem)->notifyIcon.uID,
388                                (*ptrayItem)->notifyIcon.szTip);
389   return TRUE;
390 }
391
392 static BOOL SYSTRAY_Modify(PNOTIFYICONDATAA pnid)
393 {
394   SystrayData *ptrayItem = systray.systrayItemList;
395
396   while ( ptrayItem )
397   {
398     if ( SYSTRAY_IsEqual(pnid, &ptrayItem->notifyIcon) )
399     {
400       if (pnid->uFlags & NIF_ICON)
401       {
402         SYSTRAY_SetIcon(ptrayItem, pnid->hIcon);
403         SYSTRAY_RepaintItem(ptrayItem->nitem);
404       }
405
406       if (pnid->uFlags & NIF_MESSAGE)
407         SYSTRAY_SetMessage(ptrayItem, pnid->uCallbackMessage);
408
409       if (pnid->uFlags & NIF_TIP)
410         SYSTRAY_ModifyTip(ptrayItem, pnid->szTip);
411
412       TRACE("%p: 0x%08x %d %s\n", ptrayItem, ptrayItem->notifyIcon.hWnd,
413             ptrayItem->notifyIcon.uID, ptrayItem->notifyIcon.szTip);
414       return TRUE;
415     }
416     ptrayItem = ptrayItem->nextTrayItem;
417   }
418   return FALSE; /* not found */
419 }
420
421 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid)
422 {
423   SystrayData **ptrayItem = &systray.systrayItemList;
424
425   while (*ptrayItem)
426   {
427     if (SYSTRAY_IsEqual(pnid, &(*ptrayItem)->notifyIcon))
428     {
429       SystrayData *next = (*ptrayItem)->nextTrayItem;
430       TRACE("%p: 0x%08x %d %s\n", *ptrayItem, (*ptrayItem)->notifyIcon.hWnd,
431             (*ptrayItem)->notifyIcon.uID, (*ptrayItem)->notifyIcon.szTip);
432       SYSTRAY_TermItem(*ptrayItem);
433
434           DestroyIcon  ((*ptrayItem)->notifyIcon.hIcon);
435       DestroyWindow((*ptrayItem)->hWndToolTip);
436       DestroyWindow((*ptrayItem)->hWnd);
437
438       free(*ptrayItem);
439       *ptrayItem = next;
440
441       SYSTRAY_RepaintAll();
442
443       return TRUE;
444     }
445     ptrayItem = &((*ptrayItem)->nextTrayItem);
446   }
447
448   return FALSE; /* not found */
449 }
450
451 /*************************************************************************
452  *
453  */
454 BOOL SYSTRAY_Init(void)
455 {
456   if (!systray.hasCritSection)
457   {
458     systray.hasCritSection=1;
459     InitializeCriticalSection(&systray.critSection);
460     MakeCriticalSectionGlobal(&systray.critSection);
461     TRACE(" =%p\n", &systray.critSection);
462   }
463   return TRUE;
464 }
465
466
467 /*************************************************************************
468  * Shell_NotifyIconA                    [SHELL32.297]
469  */
470 BOOL WINAPI Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA pnid )
471 {
472   BOOL flag=FALSE;
473   TRACE("wait %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
474   /* must be serialized because all apps access same systray */
475   EnterCriticalSection(&systray.critSection);
476   TRACE("enter %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
477
478
479   switch(dwMessage) {
480   case NIM_ADD:
481     TRACE("Calling systray add\n");
482     flag = SYSTRAY_Add(pnid);
483     break;
484   case NIM_MODIFY:
485     flag = SYSTRAY_Modify(pnid);
486     break;
487   case NIM_DELETE:
488     flag = SYSTRAY_Delete(pnid);
489     break;
490   }
491
492   LeaveCriticalSection(&systray.critSection);
493   TRACE("leave %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
494   return flag;
495 }
496
497 /*************************************************************************
498  * Shell_NotifyIconW                    [SHELL32.297]
499  */
500 BOOL WINAPI Shell_NotifyIconW (DWORD dwMessage, PNOTIFYICONDATAW pnid )
501 {
502         BOOL ret;
503
504         PNOTIFYICONDATAA p = HeapAlloc(GetProcessHeap(),0,sizeof(NOTIFYICONDATAA));
505         memcpy(p, pnid, sizeof(NOTIFYICONDATAA));
506         if (*(pnid->szTip))
507           lstrcpynWtoA (p->szTip, pnid->szTip, 64 );
508
509         ret = Shell_NotifyIconA(dwMessage, p );
510
511         HeapFree(GetProcessHeap(),0,p);
512         return ret;
513 }
514 /*************************************************************************
515  * Shell_NotifyIcon                     [SHELL32.296]
516  */
517 BOOL WINAPI Shell_NotifyIcon(DWORD dwMessage, PNOTIFYICONDATAA pnid)
518 {
519   return Shell_NotifyIconA(dwMessage, pnid);
520 }