Added debugstr_guid function and used it to replace
[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
27 typedef struct SystrayItem {
28   NOTIFYICONDATAA    notifyIcon;
29   int                nitem; /* number of current element = tooltip id */
30   struct SystrayItem *next; /* nextright systray item */
31 } SystrayItem;
32
33
34 typedef struct SystrayData {
35   int              hasCritSection;
36   CRITICAL_SECTION critSection;
37   HWND             hWnd;
38   HWND             hWndToolTip;
39   int              nitems; /* number of elements in systray */
40   SystrayItem      *next;  /* leftmost systray item */
41 } SystrayData;
42
43
44 static SystrayData systray;
45
46 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid);
47
48
49 /**************************************************************************
50 *  internal systray 
51 *
52 */
53 #define SMALL_ICON_SIZE GetSystemMetrics(SM_CXSMICON)
54 /* space between icons */
55 #define SMALL_ICON_FILL 1
56 /* space between icons and frame */
57 #define IBORDER 3
58 #define OBORDER 2
59 #define TBORDER  (OBORDER+1+IBORDER) 
60
61 #define ICON_TOP(i)    (TBORDER)
62 #define ICON_LEFT(i)   (TBORDER+(i)*(SMALL_ICON_SIZE+SMALL_ICON_FILL))
63 #define ICON_RIGHT(i)  (TBORDER+(i)*(SMALL_ICON_SIZE+SMALL_ICON_FILL)+SMALL_ICON_SIZE)
64 #define ICON_BOTTOM(i) (TBORDER+SMALL_ICON_SIZE)
65
66 static LRESULT CALLBACK SYSTRAY_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
67 {
68   HDC hdc;
69   PAINTSTRUCT ps;
70
71   switch (message) {
72   case WM_PAINT:
73   {
74     RECT rc;
75     SystrayItem *trayItem = systray.next;
76
77     hdc = BeginPaint(hWnd, &ps);
78     GetClientRect(hWnd, &rc);
79     /*
80     rc.top    += OBORDER;
81     rc.bottom -= OBORDER;
82     rc.left   += OBORDER;
83     rc.right  -= OBORDER;
84     DrawEdge(hdc, &rc, EDGE_SUNKEN, BF_TOPLEFT|BF_BOTTOMRIGHT);
85     */
86     rc.top = TBORDER;
87     rc.left = TBORDER;
88     while (trayItem) {
89       if (trayItem->notifyIcon.hIcon)
90         if (!DrawIconEx(hdc, rc.left, rc.top, trayItem->notifyIcon.hIcon, 
91                         SMALL_ICON_SIZE, SMALL_ICON_SIZE, 0, 0, DI_DEFAULTSIZE|DI_NORMAL))
92           SYSTRAY_Delete(&trayItem->notifyIcon);
93       trayItem = trayItem->next;
94       rc.left += SMALL_ICON_SIZE+SMALL_ICON_FILL;
95     }
96     EndPaint(hWnd, &ps);
97   } break;
98   case WM_MOUSEMOVE:
99   case WM_LBUTTONDOWN:
100   case WM_LBUTTONUP:
101   case WM_RBUTTONDOWN:
102   case WM_RBUTTONUP:
103   case WM_MBUTTONDOWN:
104   case WM_MBUTTONUP:
105   {
106     MSG msg;
107     RECT rect;
108     GetWindowRect(hWnd, &rect);
109     msg.hwnd=hWnd;
110     msg.message=message;
111     msg.wParam=wParam;
112     msg.lParam=lParam;
113     msg.time = GetMessageTime ();
114     msg.pt.x = LOWORD(GetMessagePos ());
115     msg.pt.y = HIWORD(GetMessagePos ());
116     SendMessageA(systray.hWndToolTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
117   }
118   case WM_LBUTTONDBLCLK:
119   case WM_RBUTTONDBLCLK:
120   case WM_MBUTTONDBLCLK:
121   {
122     int xPos  = LOWORD(lParam);
123     int xItem = TBORDER;
124     SystrayItem *trayItem = systray.next;
125     while (trayItem) {
126       if (xPos>=xItem && xPos<(xItem+SMALL_ICON_SIZE)) {
127         if (!PostMessageA(trayItem->notifyIcon.hWnd, trayItem->notifyIcon.uCallbackMessage,
128                           (WPARAM)trayItem->notifyIcon.hIcon, (LPARAM)message))
129           SYSTRAY_Delete(&trayItem->notifyIcon);
130         break;
131       }
132       trayItem = trayItem->next;
133       xItem += SMALL_ICON_SIZE+SMALL_ICON_FILL;
134     }
135   } break;
136   default:
137     return (DefWindowProcA(hWnd, message, wParam, lParam));
138    }
139    return (0);
140 }
141
142 BOOL SYSTRAY_Create(void)
143 {
144   WNDCLASSA  wc;
145   RECT rect;
146   wc.style         = CS_SAVEBITS;
147   wc.lpfnWndProc   = (WNDPROC)SYSTRAY_WndProc;
148   wc.cbClsExtra    = 0;
149   wc.cbWndExtra    = 0;
150   wc.hInstance     = 0;
151   wc.hIcon         = 0; /* LoadIcon (NULL, IDI_EXCLAMATION); */
152   wc.hCursor       = LoadCursorA(0, IDC_ARROWA);
153   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
154   wc.lpszMenuName  = NULL;
155   wc.lpszClassName = "WineSystray";
156   
157   TRACE("\n");
158   if (!RegisterClassA(&wc)) {
159     ERR("RegisterClass(WineSystray) failed\n");
160     return FALSE;
161   }
162   rect.left   = 0;
163   rect.top    = 0;
164   rect.right  = SMALL_ICON_SIZE+2*TBORDER;
165   rect.bottom = SMALL_ICON_SIZE+2*TBORDER;
166 /*  AdjustWindowRect(&rect, WS_CAPTION, FALSE);*/
167
168   systray.hWnd  =  CreateWindowExA(
169                                 WS_EX_TRAYWINDOW,
170                                 "WineSystray", "Wine-Systray",
171                                 WS_VISIBLE,
172                                 CW_USEDEFAULT, CW_USEDEFAULT,
173                                 rect.right-rect.left, rect.bottom-rect.top,
174                                 0, 0, 0, 0);
175   if (!systray.hWnd) {
176     ERR("CreateWindow(WineSystray) failed\n");
177     return FALSE;
178   }
179   systray.hWndToolTip = CreateWindowA(TOOLTIPS_CLASSA,NULL,TTS_ALWAYSTIP, 
180                                      CW_USEDEFAULT, CW_USEDEFAULT,
181                                      CW_USEDEFAULT, CW_USEDEFAULT, 
182                                      systray.hWnd, 0, 0, 0);
183   if (systray.hWndToolTip==0) {
184     ERR("CreateWindow(TOOLTIP) failed\n");
185     return FALSE;
186   }
187   return TRUE;
188 }
189
190
191 static void SYSTRAY_RepaintItem(int nitem)
192 {
193   if(nitem<0) { /* repaint all items */
194     RECT rc1, rc2;
195     GetWindowRect(systray.hWnd, &rc1);
196     rc2.left   = 0;
197     rc2.top    = 0;
198     rc2.right  = systray.nitems*(SMALL_ICON_SIZE+SMALL_ICON_FILL)+2*TBORDER;
199     rc2.bottom = SMALL_ICON_SIZE+2*TBORDER;
200 /*      TRACE("%d %d %d %d %d\n",systray.nitems, rc1.left, rc1.top, rc2.right-rc2.left, rc2.bottom-rc2.top); */
201 /*    AdjustWindowRect(&rc2, WS_CAPTION, FALSE);*/
202     MoveWindow(systray.hWnd, rc1.left, rc1.top, rc2.right-rc2.left, rc2.bottom-rc2.top, TRUE);
203     InvalidateRect(systray.hWnd, NULL, TRUE);
204 /*      TRACE("%d %d %d %d %d\n",systray.nitems, rc1.left, rc1.top, rc2.right-rc2.left, rc2.bottom-rc2.top); */
205   } else {
206     RECT rc;
207     rc.left   = ICON_LEFT(nitem);
208     rc.top    = ICON_TOP(nitem);
209     rc.right  = ICON_RIGHT(nitem);
210     rc.bottom = ICON_BOTTOM(nitem);
211     InvalidateRect(systray.hWnd, &rc, TRUE);
212   }
213 }
214
215 void SYSTRAY_InitItem(SystrayItem *trayItem)
216 {
217   trayItem->nitem = systray.nitems++;
218   /* create window only if needed */
219   if (systray.hWnd==0)
220     SYSTRAY_Create();
221 }
222
223 void SYSTRAY_SetIcon(SystrayItem *trayItem, HICON hIcon)
224 {
225   trayItem->notifyIcon.hIcon = hIcon;
226   SYSTRAY_RepaintItem(trayItem->nitem>systray.nitems?-1:trayItem->nitem);
227 }
228
229 void SYSTRAY_SetTip2(SystrayItem *trayItem)
230 {
231   TTTOOLINFOA ti; 
232
233   ti.cbSize = sizeof(TTTOOLINFOA);
234   ti.uFlags = 0; 
235   ti.hwnd = systray.hWnd; 
236   ti.hinst = 0; 
237   ti.uId = trayItem->nitem;
238   ti.lpszText = trayItem->notifyIcon.szTip;
239   ti.rect.left   = ICON_LEFT(trayItem->nitem);
240   ti.rect.top    = ICON_TOP(trayItem->nitem);
241   ti.rect.right  = ICON_RIGHT(trayItem->nitem);
242   ti.rect.bottom = ICON_BOTTOM(trayItem->nitem);
243   SendMessageA(systray.hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
244 }
245
246 static void SYSTRAY_TermItem(SystrayItem *removeItem)
247 {
248   int nitem;
249   SystrayItem **trayItem;
250   TTTOOLINFOA ti; 
251   ti.cbSize = sizeof(TTTOOLINFOA);
252   ti.uFlags = 0; 
253   ti.hwnd = systray.hWnd; 
254   ti.hinst = 0; 
255
256   /* delete all tooltips ...*/
257   trayItem = &systray.next;
258   while (*trayItem) {
259     ti.uId = (*trayItem)->nitem;
260     SendMessageA(systray.hWndToolTip, TTM_DELTOOLA, 0, (LPARAM)&ti);
261     trayItem = &((*trayItem)->next);
262   }
263   /* ... and add them again, because uID may shift */
264   nitem=0;
265   trayItem = &systray.next;
266   while (*trayItem) {
267     if (*trayItem != removeItem) {
268       (*trayItem)->nitem = nitem;
269       ti.uId = nitem;
270       ti.lpszText = (*trayItem)->notifyIcon.szTip;
271       ti.rect.left   = ICON_LEFT(nitem);
272       ti.rect.top    = ICON_TOP(nitem);
273       ti.rect.right  = ICON_RIGHT(nitem);
274       ti.rect.bottom = ICON_BOTTOM(nitem);
275       SendMessageA(systray.hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
276       nitem++;
277     }
278     trayItem = &((*trayItem)->next);
279   }
280   /* remove icon */
281   SYSTRAY_RepaintItem(-1);
282   systray.nitems--;
283 }
284
285 /**************************************************************************
286 *  helperfunctions 
287 *
288 */
289 void SYSTRAY_SetMessage(SystrayItem *trayItem, UINT uCallbackMessage)
290 {
291   trayItem->notifyIcon.uCallbackMessage = uCallbackMessage;
292 }
293
294
295 void SYSTRAY_SetTip(SystrayItem *trayItem, CHAR* szTip)
296 {
297 /*    char *s; */
298   strncpy(trayItem->notifyIcon.szTip, szTip, sizeof(trayItem->notifyIcon.szTip));
299   trayItem->notifyIcon.szTip[sizeof(trayItem->notifyIcon.szTip)-1]=0;
300   /* cut off trailing spaces */
301 /*
302   s=trayItem->notifyIcon.szTip+strlen(trayItem->notifyIcon.szTip);
303   while (--s >= trayItem->notifyIcon.szTip && *s == ' ')
304     *s=0;
305 */
306   SYSTRAY_SetTip2(trayItem);
307 }
308
309
310 static BOOL SYSTRAY_IsEqual(PNOTIFYICONDATAA pnid1, PNOTIFYICONDATAA pnid2)
311 {
312   if (pnid1->hWnd != pnid2->hWnd) return FALSE;
313   if (pnid1->uID  != pnid2->uID)  return FALSE;
314   return TRUE;
315 }
316
317
318 static BOOL SYSTRAY_Add(PNOTIFYICONDATAA pnid)
319 {
320   SystrayItem **trayItem = &systray.next;
321   while (*trayItem) {
322     if (SYSTRAY_IsEqual(pnid, &(*trayItem)->notifyIcon))
323       return FALSE;
324     trayItem = &((*trayItem)->next);
325   }
326   (*trayItem) = (SystrayItem*)malloc(sizeof(SystrayItem));
327   memcpy(&(*trayItem)->notifyIcon, pnid, sizeof(NOTIFYICONDATAA));
328   SYSTRAY_InitItem(*trayItem);
329   SYSTRAY_SetIcon   (*trayItem, (pnid->uFlags&NIF_ICON)   ?pnid->hIcon           :0);
330   SYSTRAY_SetMessage(*trayItem, (pnid->uFlags&NIF_MESSAGE)?pnid->uCallbackMessage:0);
331   SYSTRAY_SetTip    (*trayItem, (pnid->uFlags&NIF_TIP)    ?pnid->szTip           :"");
332   (*trayItem)->next = NULL;
333   TRACE("%p: 0x%08x %d %s\n", *trayItem, (*trayItem)->notifyIcon.hWnd,
334         (*trayItem)->notifyIcon.uID, (*trayItem)->notifyIcon.szTip);
335   return TRUE;
336 }
337     
338
339 static BOOL SYSTRAY_Modify(PNOTIFYICONDATAA pnid)
340 {
341   SystrayItem *trayItem = systray.next;
342   while (trayItem) {
343     if (SYSTRAY_IsEqual(pnid, &trayItem->notifyIcon)) {
344       if (pnid->uFlags & NIF_ICON)
345         SYSTRAY_SetIcon(trayItem, pnid->hIcon);
346       if (pnid->uFlags & NIF_MESSAGE)
347         SYSTRAY_SetMessage(trayItem, pnid->uCallbackMessage);
348       if (pnid->uFlags & NIF_TIP)
349         SYSTRAY_SetTip(trayItem, pnid->szTip);
350       TRACE("%p: 0x%08x %d %s\n", trayItem, trayItem->notifyIcon.hWnd, 
351             trayItem->notifyIcon.uID, trayItem->notifyIcon.szTip);
352       return TRUE;
353     }
354     trayItem = trayItem->next;
355   }
356   return FALSE; /* not found */
357 }
358
359
360 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid)
361 {
362   SystrayItem **trayItem = &systray.next;
363   while (*trayItem) {
364     if (SYSTRAY_IsEqual(pnid, &(*trayItem)->notifyIcon)) {
365       SystrayItem *next = (*trayItem)->next;
366       TRACE("%p: 0x%08x %d %s\n", *trayItem, (*trayItem)->notifyIcon.hWnd,
367             (*trayItem)->notifyIcon.uID, (*trayItem)->notifyIcon.szTip);
368       SYSTRAY_TermItem(*trayItem);
369       free(*trayItem);
370       *trayItem = next;
371
372       return TRUE;
373     }
374     trayItem = &((*trayItem)->next);
375   }
376
377   return FALSE; /* not found */
378 }
379
380
381 /*************************************************************************
382  *
383  */
384 BOOL SYSTRAY_Init(void)
385 {
386   if (!systray.hasCritSection) {
387     systray.hasCritSection=1;
388     InitializeCriticalSection(&systray.critSection);
389     MakeCriticalSectionGlobal(&systray.critSection);
390     TRACE(" =%p\n", &systray.critSection); 
391   }
392   return TRUE;
393 }
394
395
396 /*************************************************************************
397  * Shell_NotifyIconA                    [SHELL32.297]
398  */
399 BOOL WINAPI Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA pnid )
400 {
401   BOOL flag=FALSE;
402   TRACE("wait %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
403   /* must be serialized because all apps access same systray */
404   EnterCriticalSection(&systray.critSection);
405   TRACE("enter %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
406
407   switch(dwMessage) {
408   case NIM_ADD:
409     flag = SYSTRAY_Add(pnid);
410     break;
411   case NIM_MODIFY:
412     flag = SYSTRAY_Modify(pnid);
413     break;
414   case NIM_DELETE:
415     flag = SYSTRAY_Delete(pnid);
416     break;
417   }
418
419   LeaveCriticalSection(&systray.critSection);
420   TRACE("leave %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
421   return flag;
422 }
423
424 /*************************************************************************
425  * Shell_NotifyIconA                    [SHELL32.297]
426  */
427 BOOL WINAPI Shell_NotifyIconW (DWORD dwMessage, PNOTIFYICONDATAW pnid )
428 {
429         BOOL ret;
430
431         PNOTIFYICONDATAA p = HeapAlloc(GetProcessHeap(),0,sizeof(NOTIFYICONDATAA));
432         memcpy(p, pnid, sizeof(NOTIFYICONDATAA));
433         if (*(pnid->szTip))
434           lstrcpynWtoA (p->szTip, pnid->szTip, 64 );
435
436         ret = Shell_NotifyIconA(dwMessage, p );
437
438         HeapFree(GetProcessHeap(),0,p);
439         return ret;
440 }
441 /*************************************************************************
442  * Shell_NotifyIcon                     [SHELL32.296]
443  */
444 BOOL WINAPI Shell_NotifyIcon(DWORD dwMessage, PNOTIFYICONDATAA pnid)
445 {
446   return Shell_NotifyIconA(dwMessage, pnid);
447 }