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