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