shell32/tests: Greatly expand the test_argify() tests.
[wine] / programs / explorer / systray.c
1 /*
2  * Copyright (C) 2004 Mike Hearn, for CodeWeavers
3  * Copyright (C) 2005 Robert Shearman
4  * Copyright (C) 2008 Alexandre Julliard
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  */
20
21 #include <assert.h>
22
23 #define NONAMELESSUNION
24 #define _WIN32_IE 0x500
25 #include <windows.h>
26 #include <commctrl.h>
27 #include <shellapi.h>
28
29 #include <wine/debug.h>
30 #include <wine/list.h>
31
32 #include "explorer_private.h"
33
34 WINE_DEFAULT_DEBUG_CHANNEL(systray);
35
36 struct notify_data  /* platform-independent format for NOTIFYICONDATA */
37 {
38     LONG  hWnd;
39     UINT  uID;
40     UINT  uFlags;
41     UINT  uCallbackMessage;
42     WCHAR szTip[128];
43     DWORD dwState;
44     DWORD dwStateMask;
45     WCHAR szInfo[256];
46     union {
47         UINT uTimeout;
48         UINT uVersion;
49     } u;
50     WCHAR szInfoTitle[64];
51     DWORD dwInfoFlags;
52     GUID  guidItem;
53     /* data for the icon bitmap */
54     UINT width;
55     UINT height;
56     UINT planes;
57     UINT bpp;
58 };
59
60 static int (CDECL *wine_notify_icon)(DWORD,NOTIFYICONDATAW *);
61
62 /* an individual systray icon, unpacked from the NOTIFYICONDATA and always in unicode */
63 struct icon
64 {
65     struct list    entry;
66     HICON          image;    /* the image to render */
67     HWND           owner;    /* the HWND passed in to the Shell_NotifyIcon call */
68     HWND           tooltip;  /* Icon tooltip */
69     UINT           state;    /* state flags */
70     UINT           id;       /* the unique id given by the app */
71     UINT           callback_message;
72     int            display;  /* index in display list, or -1 if hidden */
73     WCHAR          tiptext[128]; /* Tooltip text. If empty => tooltip disabled */
74     WCHAR          info_text[256];  /* info balloon text */
75     WCHAR          info_title[64];  /* info balloon title */
76     UINT           info_flags;      /* flags for info balloon */
77     UINT           info_timeout;    /* timeout for info balloon */
78     HICON          info_icon;       /* info balloon icon */
79 };
80
81 static struct list icon_list = LIST_INIT( icon_list );
82 static HWND tray_window;
83
84 static unsigned int alloc_displayed;
85 static unsigned int nb_displayed;
86 static struct icon **displayed;  /* array of currently displayed icons */
87
88 static BOOL hide_systray;
89 static int icon_cx, icon_cy, tray_width;
90
91 static struct icon *balloon_icon;
92 static HWND balloon_window;
93
94 #define MIN_DISPLAYED 8
95 #define ICON_BORDER  2
96
97 #define VALID_WIN_TIMER      1
98 #define BALLOON_CREATE_TIMER 2
99 #define BALLOON_SHOW_TIMER   3
100
101 #define VALID_WIN_TIMEOUT        2000
102 #define BALLOON_CREATE_TIMEOUT   2000
103 #define BALLOON_SHOW_MIN_TIMEOUT 10000
104 #define BALLOON_SHOW_MAX_TIMEOUT 30000
105
106 /* Retrieves icon record by owner window and ID */
107 static struct icon *get_icon(HWND owner, UINT id)
108 {
109     struct icon *this;
110
111     /* search for the icon */
112     LIST_FOR_EACH_ENTRY( this, &icon_list, struct icon, entry )
113         if ((this->id == id) && (this->owner == owner)) return this;
114
115     return NULL;
116 }
117
118 static RECT get_icon_rect( struct icon *icon )
119 {
120     RECT rect;
121
122     rect.right = tray_width - icon_cx * icon->display;
123     rect.left = rect.right - icon_cx;
124     rect.top = 0;
125     rect.bottom = icon_cy;
126     return rect;
127 }
128
129 static void init_common_controls(void)
130 {
131     static BOOL initialized = FALSE;
132
133     if (!initialized)
134     {
135         INITCOMMONCONTROLSEX init_tooltip;
136
137         init_tooltip.dwSize = sizeof(INITCOMMONCONTROLSEX);
138         init_tooltip.dwICC = ICC_TAB_CLASSES;
139
140         InitCommonControlsEx(&init_tooltip);
141         initialized = TRUE;
142     }
143 }
144
145 /* Creates tooltip window for icon. */
146 static void create_tooltip(struct icon *icon)
147 {
148     TTTOOLINFOW ti;
149
150     init_common_controls();
151     icon->tooltip = CreateWindowExW(WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL,
152                                    WS_POPUP | TTS_ALWAYSTIP,
153                                    CW_USEDEFAULT, CW_USEDEFAULT,
154                                    CW_USEDEFAULT, CW_USEDEFAULT,
155                                    tray_window, NULL, NULL, NULL);
156
157     ZeroMemory(&ti, sizeof(ti));
158     ti.cbSize = sizeof(TTTOOLINFOW);
159     ti.hwnd = tray_window;
160     ti.lpszText = icon->tiptext;
161     if (icon->display != -1) ti.rect = get_icon_rect( icon );
162     SendMessageW(icon->tooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti);
163 }
164
165 static void set_balloon_position( struct icon *icon )
166 {
167     RECT rect = get_icon_rect( icon );
168     POINT pos;
169
170     MapWindowPoints( tray_window, 0, (POINT *)&rect, 2 );
171     pos.x = (rect.left + rect.right) / 2;
172     pos.y = (rect.top + rect.bottom) / 2;
173     SendMessageW( balloon_window, TTM_TRACKPOSITION, 0, MAKELONG( pos.x, pos.y ));
174 }
175
176 static void balloon_create_timer(void)
177 {
178     TTTOOLINFOW ti;
179
180     init_common_controls();
181     balloon_window = CreateWindowExW( WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL,
182                                       WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE,
183                                       CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
184                                       tray_window, NULL, NULL, NULL);
185
186     memset( &ti, 0, sizeof(ti) );
187     ti.cbSize = sizeof(TTTOOLINFOW);
188     ti.hwnd = tray_window;
189     ti.uFlags = TTF_TRACK;
190     ti.lpszText = balloon_icon->info_text;
191     SendMessageW( balloon_window, TTM_ADDTOOLW, 0, (LPARAM)&ti );
192     if ((balloon_icon->info_flags & NIIF_ICONMASK) == NIIF_USER)
193         SendMessageW( balloon_window, TTM_SETTITLEW, (WPARAM)balloon_icon->info_icon,
194                       (LPARAM)balloon_icon->info_title );
195     else
196         SendMessageW( balloon_window, TTM_SETTITLEW, balloon_icon->info_flags,
197                       (LPARAM)balloon_icon->info_title );
198     set_balloon_position( balloon_icon );
199     SendMessageW( balloon_window, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti );
200     KillTimer( tray_window, BALLOON_CREATE_TIMER );
201     SetTimer( tray_window, BALLOON_SHOW_TIMER, balloon_icon->info_timeout, NULL );
202 }
203
204 static BOOL show_balloon( struct icon *icon )
205 {
206     if (icon->display == -1) return FALSE;  /* not displayed */
207     if (!icon->info_text[0]) return FALSE;  /* no balloon */
208     balloon_icon = icon;
209     SetTimer( tray_window, BALLOON_CREATE_TIMER, BALLOON_CREATE_TIMEOUT, NULL );
210     return TRUE;
211 }
212
213 static void hide_balloon(void)
214 {
215     if (!balloon_icon) return;
216     if (balloon_window)
217     {
218         KillTimer( tray_window, BALLOON_SHOW_TIMER );
219         DestroyWindow( balloon_window );
220         balloon_window = 0;
221     }
222     else KillTimer( tray_window, BALLOON_CREATE_TIMER );
223     balloon_icon = NULL;
224 }
225
226 static void show_next_balloon(void)
227 {
228     struct icon *icon;
229
230     LIST_FOR_EACH_ENTRY( icon, &icon_list, struct icon, entry )
231         if (show_balloon( icon )) break;
232 }
233
234 static void update_balloon( struct icon *icon )
235 {
236     if (balloon_icon == icon)
237     {
238         hide_balloon();
239         show_balloon( icon );
240     }
241     else if (!balloon_icon)
242     {
243         if (!show_balloon( icon )) return;
244     }
245     if (!balloon_icon) show_next_balloon();
246 }
247
248 static void balloon_timer(void)
249 {
250     if (balloon_icon) balloon_icon->info_text[0] = 0;  /* clear text now that balloon has been shown */
251     hide_balloon();
252     show_next_balloon();
253 }
254
255 /* Synchronize tooltip text with tooltip window */
256 static void update_tooltip_text(struct icon *icon)
257 {
258     TTTOOLINFOW ti;
259
260     ZeroMemory(&ti, sizeof(ti));
261     ti.cbSize = sizeof(TTTOOLINFOW);
262     ti.hwnd = tray_window;
263     ti.lpszText = icon->tiptext;
264
265     SendMessageW(icon->tooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti);
266 }
267
268 /* synchronize tooltip position with tooltip window */
269 static void update_tooltip_position( struct icon *icon )
270 {
271     TTTOOLINFOW ti;
272
273     ZeroMemory(&ti, sizeof(ti));
274     ti.cbSize = sizeof(TTTOOLINFOW);
275     ti.hwnd = tray_window;
276     if (icon->display != -1) ti.rect = get_icon_rect( icon );
277     SendMessageW( icon->tooltip, TTM_NEWTOOLRECTW, 0, (LPARAM)&ti );
278     if (balloon_icon == icon) set_balloon_position( icon );
279 }
280
281 /* find the icon located at a certain point in the tray window */
282 static struct icon *icon_from_point( int x, int y )
283 {
284     if (y < 0 || y >= icon_cy) return NULL;
285     x = tray_width - x;
286     if (x < 0 || x >= icon_cx * nb_displayed) return NULL;
287     return displayed[x / icon_cx];
288 }
289
290 /* invalidate the portion of the tray window that contains the specified icons */
291 static void invalidate_icons( unsigned int start, unsigned int end )
292 {
293     RECT rect;
294
295     rect.left = tray_width - (end + 1) * icon_cx;
296     rect.top  = 0;
297     rect.right = tray_width - start * icon_cx;
298     rect.bottom = icon_cy;
299     InvalidateRect( tray_window, &rect, TRUE );
300 }
301
302 /* make an icon visible */
303 static BOOL show_icon(struct icon *icon)
304 {
305     WINE_TRACE("id=0x%x, hwnd=%p\n", icon->id, icon->owner);
306
307     if (icon->display != -1) return TRUE;  /* already displayed */
308
309     if (nb_displayed >= alloc_displayed)
310     {
311         unsigned int new_count = max( alloc_displayed * 2, 32 );
312         struct icon **ptr;
313         if (displayed) ptr = HeapReAlloc( GetProcessHeap(), 0, displayed, new_count * sizeof(*ptr) );
314         else ptr = HeapAlloc( GetProcessHeap(), 0, new_count * sizeof(*ptr) );
315         if (!ptr) return FALSE;
316         displayed = ptr;
317         alloc_displayed = new_count;
318     }
319
320     icon->display = nb_displayed;
321     displayed[nb_displayed++] = icon;
322     update_tooltip_position( icon );
323     invalidate_icons( nb_displayed-1, nb_displayed-1 );
324
325     if (nb_displayed == 1 && !hide_systray) ShowWindow( tray_window, SW_SHOWNA );
326
327     create_tooltip(icon);
328     update_balloon( icon );
329     return TRUE;
330 }
331
332 /* make an icon invisible */
333 static BOOL hide_icon(struct icon *icon)
334 {
335     unsigned int i;
336
337     WINE_TRACE("id=0x%x, hwnd=%p\n", icon->id, icon->owner);
338
339     if (icon->display == -1) return TRUE;  /* already hidden */
340
341     assert( nb_displayed );
342     for (i = icon->display; i < nb_displayed - 1; i++)
343     {
344         displayed[i] = displayed[i + 1];
345         displayed[i]->display = i;
346         update_tooltip_position( displayed[i] );
347     }
348     nb_displayed--;
349     invalidate_icons( icon->display, nb_displayed );
350     icon->display = -1;
351
352     if (!nb_displayed) ShowWindow( tray_window, SW_HIDE );
353
354     update_balloon( icon );
355     update_tooltip_position( icon );
356     return TRUE;
357 }
358
359 /* Modifies an existing icon record */
360 static BOOL modify_icon( struct icon *icon, NOTIFYICONDATAW *nid )
361 {
362     WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd);
363
364     /* demarshal the request from the NID */
365     if (!icon)
366     {
367         WINE_WARN("Invalid icon ID (0x%x) for HWND %p\n", nid->uID, nid->hWnd);
368         return FALSE;
369     }
370
371     if (nid->uFlags & NIF_STATE)
372     {
373         icon->state = (icon->state & ~nid->dwStateMask) | (nid->dwState & nid->dwStateMask);
374     }
375
376     if (nid->uFlags & NIF_ICON)
377     {
378         if (icon->image) DestroyIcon(icon->image);
379         icon->image = CopyIcon(nid->hIcon);
380         if (icon->display != -1) invalidate_icons( icon->display, icon->display );
381     }
382
383     if (nid->uFlags & NIF_MESSAGE)
384     {
385         icon->callback_message = nid->uCallbackMessage;
386     }
387     if (nid->uFlags & NIF_TIP)
388     {
389         lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR));
390         if (icon->display != -1) update_tooltip_text(icon);
391     }
392     if (nid->uFlags & NIF_INFO && nid->cbSize >= NOTIFYICONDATAA_V2_SIZE)
393     {
394         lstrcpynW( icon->info_text, nid->szInfo, sizeof(icon->info_text)/sizeof(WCHAR) );
395         lstrcpynW( icon->info_title, nid->szInfoTitle, sizeof(icon->info_title)/sizeof(WCHAR) );
396         icon->info_flags = nid->dwInfoFlags;
397         icon->info_timeout = max(min(nid->u.uTimeout, BALLOON_SHOW_MAX_TIMEOUT), BALLOON_SHOW_MIN_TIMEOUT);
398         icon->info_icon = nid->hBalloonIcon;
399         update_balloon( icon );
400     }
401     if (icon->state & NIS_HIDDEN) hide_icon( icon );
402     else show_icon( icon );
403     return TRUE;
404 }
405
406 /* Adds a new icon record to the list */
407 static BOOL add_icon(NOTIFYICONDATAW *nid)
408 {
409     struct icon  *icon;
410
411     WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd);
412
413     if ((icon = get_icon(nid->hWnd, nid->uID)))
414     {
415         WINE_WARN("duplicate tray icon add, buggy app?\n");
416         return FALSE;
417     }
418
419     if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon))))
420     {
421         WINE_ERR("out of memory\n");
422         return FALSE;
423     }
424
425     ZeroMemory(icon, sizeof(struct icon));
426     icon->id     = nid->uID;
427     icon->owner  = nid->hWnd;
428     icon->display = -1;
429
430     if (list_empty( &icon_list )) SetTimer( tray_window, VALID_WIN_TIMER, VALID_WIN_TIMEOUT, NULL );
431     list_add_tail(&icon_list, &icon->entry);
432
433     return modify_icon( icon, nid );
434 }
435
436 /* Deletes tray icon window and icon record */
437 static BOOL delete_icon(struct icon *icon)
438 {
439     hide_icon(icon);
440     list_remove(&icon->entry);
441     DestroyIcon(icon->image);
442     HeapFree(GetProcessHeap(), 0, icon);
443     if (list_empty( &icon_list )) KillTimer( tray_window, VALID_WIN_TIMER );
444     return TRUE;
445 }
446
447 /* cleanup icons belonging to windows that have been destroyed */
448 static void cleanup_destroyed_windows(void)
449 {
450     struct icon *icon, *next;
451
452     LIST_FOR_EACH_ENTRY_SAFE( icon, next, &icon_list, struct icon, entry )
453         if (!IsWindow( icon->owner )) delete_icon( icon );
454 }
455
456 static BOOL handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds)
457 {
458     struct icon *icon = NULL;
459     const struct notify_data *data;
460     NOTIFYICONDATAW nid;
461     int ret = FALSE;
462
463     if (cds->cbData < sizeof(*data)) return FALSE;
464     data = cds->lpData;
465
466     nid.cbSize           = sizeof(nid);
467     nid.hWnd             = LongToHandle( data->hWnd );
468     nid.uID              = data->uID;
469     nid.uFlags           = data->uFlags;
470     nid.uCallbackMessage = data->uCallbackMessage;
471     nid.hIcon            = 0;
472     nid.dwState          = data->dwState;
473     nid.dwStateMask      = data->dwStateMask;
474     nid.u.uTimeout       = data->u.uTimeout;
475     nid.dwInfoFlags      = data->dwInfoFlags;
476     nid.guidItem         = data->guidItem;
477     lstrcpyW( nid.szTip, data->szTip );
478     lstrcpyW( nid.szInfo, data->szInfo );
479     lstrcpyW( nid.szInfoTitle, data->szInfoTitle );
480     nid.hBalloonIcon     = 0;
481
482     /* FIXME: if statement only needed because we don't support interprocess
483      * icon handles */
484     if ((nid.uFlags & NIF_ICON) && cds->cbData > sizeof(*data))
485     {
486         LONG cbMaskBits;
487         LONG cbColourBits;
488         const char *buffer = (const char *)(data + 1);
489
490         cbMaskBits = (data->width * data->height + 15) / 16 * 2;
491         cbColourBits = (data->planes * data->width * data->height * data->bpp + 15) / 16 * 2;
492
493         if (cds->cbData < sizeof(*data) + cbMaskBits + cbColourBits)
494         {
495             WINE_ERR("buffer underflow\n");
496             return FALSE;
497         }
498         nid.hIcon = CreateIcon(NULL, data->width, data->height, data->planes, data->bpp,
499                                buffer, buffer + cbMaskBits);
500     }
501
502     /* try forward to x11drv first */
503     if (cds->dwData == NIM_ADD || !(icon = get_icon( nid.hWnd, nid.uID )))
504     {
505         if (wine_notify_icon && ((ret = wine_notify_icon( cds->dwData, &nid )) != -1))
506         {
507             if (nid.hIcon) DestroyIcon( nid.hIcon );
508             return ret;
509         }
510         ret = FALSE;
511     }
512
513     switch (cds->dwData)
514     {
515     case NIM_ADD:
516         ret = add_icon(&nid);
517         break;
518     case NIM_DELETE:
519         if (icon) ret = delete_icon( icon );
520         break;
521     case NIM_MODIFY:
522         if (icon) ret = modify_icon( icon, &nid );
523         break;
524     default:
525         WINE_FIXME("unhandled tray message: %ld\n", cds->dwData);
526         break;
527     }
528
529     if (nid.hIcon) DestroyIcon( nid.hIcon );
530     return ret;
531 }
532
533 static void do_hide_systray(void)
534 {
535     SetWindowPos( tray_window, 0,
536                   GetSystemMetrics(SM_XVIRTUALSCREEN) + GetSystemMetrics(SM_CXVIRTUALSCREEN),
537                   GetSystemMetrics(SM_YVIRTUALSCREEN) + GetSystemMetrics(SM_CYVIRTUALSCREEN),
538                   0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE );
539 }
540
541 static LRESULT WINAPI tray_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
542 {
543     switch (msg)
544     {
545     case WM_COPYDATA:
546         return handle_incoming((HWND)wparam, (COPYDATASTRUCT *)lparam);
547
548     case WM_DISPLAYCHANGE:
549         if (hide_systray) do_hide_systray();
550         else
551         {
552             tray_width = GetSystemMetrics( SM_CXSCREEN );
553             SetWindowPos( tray_window, 0, 0, GetSystemMetrics( SM_CYSCREEN ) - icon_cy,
554                           tray_width, icon_cy, SWP_NOZORDER | SWP_NOACTIVATE );
555         }
556         break;
557
558     case WM_TIMER:
559         switch (wparam)
560         {
561         case VALID_WIN_TIMER:      cleanup_destroyed_windows(); break;
562         case BALLOON_CREATE_TIMER: balloon_create_timer(); break;
563         case BALLOON_SHOW_TIMER:   balloon_timer(); break;
564         }
565         break;
566
567     case WM_PAINT:
568         {
569             unsigned int i;
570             PAINTSTRUCT ps;
571             HDC hdc;
572
573             hdc = BeginPaint( hwnd, &ps );
574             for (i = 0; i < nb_displayed; i++)
575             {
576                 RECT dummy, rect = get_icon_rect( displayed[i] );
577                 if (IntersectRect( &dummy, &rect, &ps.rcPaint ))
578                     DrawIconEx( hdc, rect.left + ICON_BORDER, rect.top + ICON_BORDER, displayed[i]->image,
579                                 icon_cx - 2*ICON_BORDER, icon_cy - 2*ICON_BORDER,
580                             0, 0, DI_DEFAULTSIZE|DI_NORMAL);
581             }
582             EndPaint( hwnd, &ps );
583             break;
584         }
585
586     case WM_MOUSEMOVE:
587     case WM_LBUTTONDOWN:
588     case WM_LBUTTONUP:
589     case WM_RBUTTONDOWN:
590     case WM_RBUTTONUP:
591     case WM_MBUTTONDOWN:
592     case WM_MBUTTONUP:
593     case WM_LBUTTONDBLCLK:
594     case WM_RBUTTONDBLCLK:
595     case WM_MBUTTONDBLCLK:
596         {
597             MSG message;
598             struct icon *icon = icon_from_point( (short)LOWORD(lparam), (short)HIWORD(lparam) );
599             if (!icon) break;
600
601             /* notify the owner hwnd of the message */
602             WINE_TRACE("relaying 0x%x\n", msg);
603
604             message.hwnd = hwnd;
605             message.message = msg;
606             message.wParam = wparam;
607             message.lParam = lparam;
608             SendMessageW( icon->tooltip, TTM_RELAYEVENT, 0, (LPARAM)&message );
609
610             if (!PostMessageW( icon->owner, icon->callback_message, (WPARAM) icon->id, (LPARAM) msg ) &&
611                 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
612             {
613                 WINE_WARN("application window was destroyed without removing "
614                           "notification icon, removing automatically\n");
615                 delete_icon( icon );
616             }
617             break;
618         }
619
620     case WM_CLOSE:
621         /* don't destroy the tray window, just hide it */
622         ShowWindow( hwnd, SW_HIDE );
623         return 0;
624
625     default:
626         return DefWindowProcW( hwnd, msg, wparam, lparam );
627     }
628     return 0;
629 }
630
631 /* this function creates the listener window */
632 void initialize_systray( BOOL using_root )
633 {
634     HMODULE x11drv;
635     WNDCLASSEXW class;
636     static const WCHAR classname[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0};
637
638     if ((x11drv = GetModuleHandleA( "winex11.drv" )))
639         wine_notify_icon = (void *)GetProcAddress( x11drv, "wine_notify_icon" );
640
641     icon_cx = GetSystemMetrics( SM_CXSMICON ) + 2*ICON_BORDER;
642     icon_cy = GetSystemMetrics( SM_CYSMICON ) + 2*ICON_BORDER;
643     hide_systray = using_root;
644
645     /* register the systray listener window class */
646     ZeroMemory(&class, sizeof(class));
647     class.cbSize        = sizeof(class);
648     class.style         = CS_DBLCLKS | CS_HREDRAW;
649     class.lpfnWndProc   = tray_wndproc;
650     class.hInstance     = NULL;
651     class.hIcon         = LoadIconW(0, (LPCWSTR)IDI_WINLOGO);
652     class.hCursor       = LoadCursorW(0, (LPCWSTR)IDC_ARROW);
653     class.hbrBackground = (HBRUSH) COLOR_WINDOW;
654     class.lpszClassName = (WCHAR *) &classname;
655
656     if (!RegisterClassExW(&class))
657     {
658         WINE_ERR("Could not register SysTray window class\n");
659         return;
660     }
661
662     tray_width = GetSystemMetrics( SM_CXSCREEN );
663     tray_window = CreateWindowExW( WS_EX_NOACTIVATE, classname, NULL, WS_POPUP,
664                                    0, GetSystemMetrics( SM_CYSCREEN ) - icon_cy,
665                                    tray_width, icon_cy, 0, 0, 0, 0 );
666     if (!tray_window)
667     {
668         WINE_ERR("Could not create tray window\n");
669         return;
670     }
671
672     if (hide_systray) do_hide_systray();
673 }