Fix typos in comments.
[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 "winnls.h"
17 #include "shlobj.h"
18 #include "shellapi.h"
19 #include "shell32_main.h"
20 #include "commctrl.h"
21 #include "debugtools.h"
22 #include "heap.h"
23 #include "config.h"
24
25 DEFAULT_DEBUG_CHANNEL(shell);
26
27 typedef struct SystrayItem {
28   HWND                  hWnd;
29   HWND                  hWndToolTip;
30   NOTIFYICONDATAA       notifyIcon;
31   struct SystrayItem    *nextTrayItem;
32 } SystrayItem;
33
34 static SystrayItem *systray=NULL;
35 static int firstSystray=TRUE; /* defer creation of window class until first systray item is created */
36
37 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid);
38
39
40 #define ICON_SIZE GetSystemMetrics(SM_CXSMICON)
41 /* space around icon (forces icon to center of KDE systray area) */
42 #define ICON_BORDER  4
43
44
45
46 static BOOL SYSTRAY_ItemIsEqual(PNOTIFYICONDATAA pnid1, PNOTIFYICONDATAA pnid2)
47 {
48   if (pnid1->hWnd != pnid2->hWnd) return FALSE;
49   if (pnid1->uID  != pnid2->uID)  return FALSE;
50   return TRUE;
51 }
52
53 static LRESULT CALLBACK SYSTRAY_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
54 {
55   HDC hdc;
56   PAINTSTRUCT ps;
57
58   switch (message) {
59   case WM_PAINT:
60   {
61     RECT rc;
62     SystrayItem  *ptrayItem = systray;
63
64     while (ptrayItem) {
65       if (ptrayItem->hWnd==hWnd) {
66         if (ptrayItem->notifyIcon.hIcon) {
67           hdc = BeginPaint(hWnd, &ps);
68           GetClientRect(hWnd, &rc);
69           if (!DrawIconEx(hdc, rc.left+ICON_BORDER, rc.top+ICON_BORDER, ptrayItem->notifyIcon.hIcon,
70                           ICON_SIZE, ICON_SIZE, 0, 0, DI_DEFAULTSIZE|DI_NORMAL)) {
71             ERR("Paint(SystrayWindow 0x%08x) failed -> removing SystrayItem %p\n", hWnd, ptrayItem);
72             SYSTRAY_Delete(&ptrayItem->notifyIcon);
73           }
74         }
75         break;
76       }
77       ptrayItem = ptrayItem->nextTrayItem;
78     }
79     EndPaint(hWnd, &ps);
80   }
81   break;
82
83   case WM_MOUSEMOVE:
84   case WM_LBUTTONDOWN:
85   case WM_LBUTTONUP:
86   case WM_RBUTTONDOWN:
87   case WM_RBUTTONUP:
88   case WM_MBUTTONDOWN:
89   case WM_MBUTTONUP:
90   {
91     MSG msg;
92     SystrayItem *ptrayItem = systray;
93
94     while ( ptrayItem ) {
95       if (ptrayItem->hWnd == hWnd) {
96         msg.hwnd=hWnd;
97         msg.message=message;
98         msg.wParam=wParam;
99         msg.lParam=lParam;
100         msg.time = GetMessageTime ();
101         msg.pt.x = LOWORD(GetMessagePos ());
102         msg.pt.y = HIWORD(GetMessagePos ());
103
104         SendMessageA(ptrayItem->hWndToolTip, TTM_RELAYEVENT, 0, (LPARAM)&msg);
105       }
106       ptrayItem = ptrayItem->nextTrayItem;
107     }
108   }
109   /* fallthru */
110
111   case WM_LBUTTONDBLCLK:
112   case WM_RBUTTONDBLCLK:
113   case WM_MBUTTONDBLCLK:
114   {
115     SystrayItem *ptrayItem = systray;
116
117     while (ptrayItem) {
118       if (ptrayItem->hWnd == hWnd) {
119         if (ptrayItem->notifyIcon.hWnd && ptrayItem->notifyIcon.uCallbackMessage) {
120           if (!PostMessageA(ptrayItem->notifyIcon.hWnd, ptrayItem->notifyIcon.uCallbackMessage,
121                             (WPARAM)ptrayItem->notifyIcon.uID, (LPARAM)message)) {
122               ERR("PostMessage(SystrayWindow 0x%08x) failed -> removing SystrayItem %p\n", hWnd, ptrayItem);
123               SYSTRAY_Delete(&ptrayItem->notifyIcon);
124             }
125         }
126         break;
127       }
128       ptrayItem = ptrayItem->nextTrayItem;
129     }
130   }
131   break;
132
133   default:
134     return (DefWindowProcA(hWnd, message, wParam, lParam));
135   }
136   return (0);
137
138 }
139
140
141 BOOL SYSTRAY_RegisterClass(void)
142 {
143   WNDCLASSA  wc;
144
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;
151   wc.hCursor       = LoadCursorA(0, IDC_ARROWA);
152   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
153   wc.lpszMenuName  = NULL;
154   wc.lpszClassName = "WineSystray";
155
156   if (!RegisterClassA(&wc)) {
157     ERR("RegisterClass(WineSystray) failed\n");
158     return FALSE;
159   }
160   return TRUE;
161 }
162
163
164 BOOL SYSTRAY_ItemInit(SystrayItem *ptrayItem)
165 {
166   RECT rect;
167
168   /* Register the class if this is our first tray item. */
169   if ( firstSystray ) {
170     firstSystray = FALSE;
171     if ( !SYSTRAY_RegisterClass() ) {
172       ERR( "RegisterClass(WineSystray) failed\n" );
173       return FALSE;
174     }
175   }
176
177   /* Initialize the window size. */
178   rect.left   = 0;
179   rect.top    = 0;
180   rect.right  = ICON_SIZE+2*ICON_BORDER;
181   rect.bottom = ICON_SIZE+2*ICON_BORDER;
182
183   ZeroMemory( ptrayItem, sizeof(SystrayItem) );
184   /* Create tray window for icon. */
185   ptrayItem->hWnd = CreateWindowExA( WS_EX_TRAYWINDOW,
186                                 "WineSystray", "Wine-Systray",
187                                 WS_VISIBLE,
188                                 CW_USEDEFAULT, CW_USEDEFAULT,
189                                 rect.right-rect.left, rect.bottom-rect.top,
190                                 0, 0, 0, 0 );
191   if ( !ptrayItem->hWnd ) {
192     ERR( "CreateWindow(WineSystray) failed\n" );
193     return FALSE;
194   }
195
196   /* Create tooltip for icon. */
197   ptrayItem->hWndToolTip = CreateWindowA( TOOLTIPS_CLASSA,NULL,TTS_ALWAYSTIP,
198                                      CW_USEDEFAULT, CW_USEDEFAULT,
199                                      CW_USEDEFAULT, CW_USEDEFAULT,
200                                      ptrayItem->hWnd, 0, 0, 0 );
201   if ( !ptrayItem->hWndToolTip ) {
202     ERR( "CreateWindow(TOOLTIP) failed\n" );
203     return FALSE;
204   }
205   return TRUE;
206 }
207
208
209 static void SYSTRAY_ItemTerm(SystrayItem *ptrayItem)
210 {
211   if(ptrayItem->notifyIcon.hIcon)
212      DestroyIcon(ptrayItem->notifyIcon.hIcon);   
213   if(ptrayItem->hWndToolTip)
214       DestroyWindow(ptrayItem->hWndToolTip);
215   if(ptrayItem->hWnd)
216     DestroyWindow(ptrayItem->hWnd);
217   return;
218 }
219
220
221 void SYSTRAY_ItemSetMessage(SystrayItem *ptrayItem, UINT uCallbackMessage)
222 {
223   ptrayItem->notifyIcon.uCallbackMessage = uCallbackMessage;
224 }
225
226
227 void SYSTRAY_ItemSetIcon(SystrayItem *ptrayItem, HICON hIcon)
228 {
229   ptrayItem->notifyIcon.hIcon = CopyIcon(hIcon);
230   InvalidateRect(ptrayItem->hWnd, NULL, TRUE);
231 }
232
233
234 void SYSTRAY_ItemSetTip(SystrayItem *ptrayItem, CHAR* szTip, int modify)
235 {
236   TTTOOLINFOA ti;
237
238   strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip));
239   ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0;
240
241   ti.cbSize = sizeof(TTTOOLINFOA);
242   ti.uFlags = 0;
243   ti.hwnd = ptrayItem->hWnd;
244   ti.hinst = 0;
245   ti.uId = 0;
246   ti.lpszText = ptrayItem->notifyIcon.szTip;
247   ti.rect.left   = 0;
248   ti.rect.top    = 0;
249   ti.rect.right  = ICON_SIZE+2*ICON_BORDER;
250   ti.rect.bottom = ICON_SIZE+2*ICON_BORDER;
251
252   if(modify)
253     SendMessageA(ptrayItem->hWndToolTip, TTM_UPDATETIPTEXTA, 0, (LPARAM)&ti);
254   else
255     SendMessageA(ptrayItem->hWndToolTip, TTM_ADDTOOLA, 0, (LPARAM)&ti);
256 }
257
258
259 static BOOL SYSTRAY_Add(PNOTIFYICONDATAA pnid)
260 {
261   SystrayItem **ptrayItem = &systray;
262
263   /* Find last element. */
264   while( *ptrayItem ) {
265     if ( SYSTRAY_ItemIsEqual(pnid, &(*ptrayItem)->notifyIcon) )
266       return FALSE;
267     ptrayItem = &((*ptrayItem)->nextTrayItem);
268   }
269   /* Allocate SystrayItem for element and add to end of list. */
270   (*ptrayItem) = ( SystrayItem *)malloc( sizeof(SystrayItem) );
271
272   /* Initialize and set data for the tray element. */
273   SYSTRAY_ItemInit( (*ptrayItem) );
274   (*ptrayItem)->notifyIcon.uID = pnid->uID; /* only needed for callback message */
275   (*ptrayItem)->notifyIcon.hWnd = pnid->hWnd; /* only needed for callback message */
276   SYSTRAY_ItemSetIcon   (*ptrayItem, (pnid->uFlags&NIF_ICON)   ?pnid->hIcon           :0);
277   SYSTRAY_ItemSetMessage(*ptrayItem, (pnid->uFlags&NIF_MESSAGE)?pnid->uCallbackMessage:0);
278   SYSTRAY_ItemSetTip    (*ptrayItem, (pnid->uFlags&NIF_TIP)    ?pnid->szTip           :"", FALSE);
279
280   TRACE("%p: 0x%08x %s\n",  (*ptrayItem), (*ptrayItem)->notifyIcon.hWnd,
281                                           (*ptrayItem)->notifyIcon.szTip);
282   return TRUE;
283 }
284
285
286 static BOOL SYSTRAY_Modify(PNOTIFYICONDATAA pnid)
287 {
288   SystrayItem *ptrayItem = systray;
289
290   while ( ptrayItem ) {
291     if ( SYSTRAY_ItemIsEqual(pnid, &ptrayItem->notifyIcon) ) {
292       if (pnid->uFlags & NIF_ICON)
293         SYSTRAY_ItemSetIcon(ptrayItem, pnid->hIcon);
294       if (pnid->uFlags & NIF_MESSAGE)
295         SYSTRAY_ItemSetMessage(ptrayItem, pnid->uCallbackMessage);
296       if (pnid->uFlags & NIF_TIP)
297         SYSTRAY_ItemSetTip(ptrayItem, pnid->szTip, TRUE);
298
299       TRACE("%p: 0x%08x %s\n", ptrayItem, ptrayItem->notifyIcon.hWnd, ptrayItem->notifyIcon.szTip);
300       return TRUE;
301     }
302     ptrayItem = ptrayItem->nextTrayItem;
303   }
304   return FALSE; /* not found */
305 }
306
307
308 static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid)
309 {
310   SystrayItem **ptrayItem = &systray;
311
312   while (*ptrayItem) {
313     if (SYSTRAY_ItemIsEqual(pnid, &(*ptrayItem)->notifyIcon)) {
314       SystrayItem *next = (*ptrayItem)->nextTrayItem;
315       TRACE("%p: 0x%08x %s\n", *ptrayItem, (*ptrayItem)->notifyIcon.hWnd, (*ptrayItem)->notifyIcon.szTip);
316       SYSTRAY_ItemTerm(*ptrayItem);
317
318       free(*ptrayItem);
319       *ptrayItem = next;
320
321       return TRUE;
322     }
323     ptrayItem = &((*ptrayItem)->nextTrayItem);
324   }
325
326   return FALSE; /* not found */
327 }
328
329 /*************************************************************************
330  *
331  */
332 BOOL SYSTRAY_Init(void)
333 {
334   return TRUE;
335 }
336
337 /*************************************************************************
338  * Shell_NotifyIconA                    [SHELL32.297][SHELL32.296]
339  */
340 BOOL WINAPI Shell_NotifyIconA(DWORD dwMessage, PNOTIFYICONDATAA pnid )
341 {
342   BOOL flag=FALSE;
343   TRACE("enter %d %d %ld\n", pnid->hWnd, pnid->uID, dwMessage);
344   switch(dwMessage) {
345   case NIM_ADD:
346     flag = SYSTRAY_Add(pnid);
347     break;
348   case NIM_MODIFY:
349     flag = SYSTRAY_Modify(pnid);
350     break;
351   case NIM_DELETE:
352     flag = SYSTRAY_Delete(pnid);
353     break;
354   }
355   TRACE("leave %d %d %ld=%d\n", pnid->hWnd, pnid->uID, dwMessage, flag);
356   return flag;
357 }
358
359 /*************************************************************************
360  * Shell_NotifyIconW                    [SHELL32.298]
361  */
362 BOOL WINAPI Shell_NotifyIconW (DWORD dwMessage, PNOTIFYICONDATAW pnid )
363 {
364         BOOL ret;
365
366         PNOTIFYICONDATAA p = HeapAlloc(GetProcessHeap(),0,sizeof(NOTIFYICONDATAA));
367         memcpy(p, pnid, sizeof(NOTIFYICONDATAA));
368         WideCharToMultiByte( CP_ACP, 0, pnid->szTip, -1, p->szTip, sizeof(p->szTip), NULL, NULL );
369         p->szTip[sizeof(p->szTip)-1] = 0;
370
371         ret = Shell_NotifyIconA(dwMessage, p );
372
373         HeapFree(GetProcessHeap(),0,p);
374         return ret;
375 }