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