winex11: Create Win32 windows corresponding to all the ancestors of embedded windows.
[wine] / dlls / winex11.drv / systray.c
1 /*
2  * X11 system tray management
3  *
4  * Copyright (C) 2004 Mike Hearn, for CodeWeavers
5  * Copyright (C) 2005 Robert Shearman
6  * Copyright (C) 2008 Alexandre Julliard
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22
23 #include "config.h"
24
25 #include <assert.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <stdio.h>
29 #ifdef HAVE_UNISTD_H
30 # include <unistd.h>
31 #endif
32
33 #include <X11/Xlib.h>
34
35 #include "windef.h"
36 #include "winbase.h"
37 #include "wingdi.h"
38 #include "winuser.h"
39 #include "commctrl.h"
40 #include "shellapi.h"
41
42 #include "x11drv.h"
43 #include "wine/list.h"
44 #include "wine/debug.h"
45
46 WINE_DEFAULT_DEBUG_CHANNEL(systray);
47
48 /* an individual systray icon */
49 struct tray_icon
50 {
51     struct list    entry;
52     HICON          image;    /* the image to render */
53     HWND           owner;    /* the HWND passed in to the Shell_NotifyIcon call */
54     HWND           window;   /* the adaptor window */
55     HWND           tooltip;  /* Icon tooltip */
56     UINT           id;       /* the unique id given by the app */
57     UINT           callback_message;
58     int            display;  /* display index, or -1 if hidden */
59     WCHAR          tiptext[256]; /* Tooltip text. If empty => tooltip disabled */
60     WCHAR          tiptitle[64]; /* Tooltip title for ballon style tooltips.  If empty => tooltip is not balloon style. */
61 };
62
63 static struct list icon_list = LIST_INIT( icon_list );
64
65 static const WCHAR icon_classname[] = {'_','_','w','i','n','e','x','1','1','_','t','r','a','y','_','i','c','o','n',0};
66 static const WCHAR tray_classname[] = {'_','_','w','i','n','e','x','1','1','_','s','t','a','n','d','a','l','o','n','e','_','t','r','a','y',0};
67
68 static BOOL show_icon( struct tray_icon *icon );
69 static BOOL hide_icon( struct tray_icon *icon );
70 static BOOL delete_icon( struct tray_icon *icon );
71
72 #define SYSTEM_TRAY_REQUEST_DOCK  0
73 #define SYSTEM_TRAY_BEGIN_MESSAGE   1
74 #define SYSTEM_TRAY_CANCEL_MESSAGE  2
75
76 Atom systray_atom = 0;
77
78 #define MIN_DISPLAYED 8
79 #define ICON_BORDER 2
80
81 /* stand-alone tray window */
82 static HWND standalone_tray;
83 static int icon_cx, icon_cy;
84 static unsigned int nb_displayed;
85
86 /* retrieves icon record by owner window and ID */
87 static struct tray_icon *get_icon(HWND owner, UINT id)
88 {
89     struct tray_icon *this;
90
91     LIST_FOR_EACH_ENTRY( this, &icon_list, struct tray_icon, entry )
92         if ((this->id == id) && (this->owner == owner)) return this;
93     return NULL;
94 }
95
96 /* create tooltip window for icon */
97 static void create_tooltip(struct tray_icon *icon)
98 {
99     static BOOL tooltips_initialized = FALSE;
100
101     if (!tooltips_initialized)
102     {
103         INITCOMMONCONTROLSEX init_tooltip;
104
105         init_tooltip.dwSize = sizeof(INITCOMMONCONTROLSEX);
106         init_tooltip.dwICC = ICC_TAB_CLASSES;
107
108         InitCommonControlsEx(&init_tooltip);
109         tooltips_initialized = TRUE;
110     }
111     if (icon->tiptitle[0] != 0)
112     {
113         icon->tooltip = CreateWindowExW( WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL,
114                                          WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
115                                          CW_USEDEFAULT, CW_USEDEFAULT,
116                                          CW_USEDEFAULT, CW_USEDEFAULT,
117                                          icon->window, NULL, NULL, NULL);
118     }
119     else
120     {
121         icon->tooltip = CreateWindowExW( WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL,
122                                          WS_POPUP | TTS_ALWAYSTIP,
123                                          CW_USEDEFAULT, CW_USEDEFAULT,
124                                          CW_USEDEFAULT, CW_USEDEFAULT,
125                                          icon->window, NULL, NULL, NULL);
126     }
127     if (icon->tooltip)
128     {
129         TTTOOLINFOW ti;
130         ZeroMemory(&ti, sizeof(ti));
131         ti.cbSize = sizeof(TTTOOLINFOW);
132         ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND;
133         ti.hwnd = icon->window;
134         ti.uId = (UINT_PTR)icon->window;
135         ti.lpszText = icon->tiptext;
136         SendMessageW(icon->tooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti);
137     }
138 }
139
140 /* synchronize tooltip text with tooltip window */
141 static void update_tooltip_text(struct tray_icon *icon)
142 {
143     TTTOOLINFOW ti;
144
145     ZeroMemory(&ti, sizeof(ti));
146     ti.cbSize = sizeof(TTTOOLINFOW);
147     ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND;
148     ti.hwnd = icon->window;
149     ti.uId = (UINT_PTR)icon->window;
150     ti.lpszText = icon->tiptext;
151
152     SendMessageW(icon->tooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti);
153 }
154
155 /* get the size of the stand-alone tray window */
156 static SIZE get_window_size(void)
157 {
158     SIZE size;
159     RECT rect;
160
161     rect.left = 0;
162     rect.top = 0;
163     rect.right = icon_cx * max( nb_displayed, MIN_DISPLAYED );
164     rect.bottom = icon_cy;
165     AdjustWindowRect( &rect, WS_CAPTION, FALSE );
166     size.cx = rect.right - rect.left;
167     size.cy = rect.bottom - rect.top;
168     return size;
169 }
170
171 /* get the position of an icon in the stand-alone tray */
172 static POINT get_icon_pos( struct tray_icon *icon )
173 {
174     POINT pos;
175
176     pos.x = icon_cx * icon->display;
177     pos.y = 0;
178     return pos;
179 }
180
181 /* window procedure for the standalone tray window */
182 static LRESULT WINAPI standalone_tray_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam )
183 {
184     switch (msg)
185     {
186     case WM_CLOSE:
187         ShowWindow( hwnd, SW_HIDE );
188         show_systray = FALSE;
189         return 0;
190     case WM_DESTROY:
191         standalone_tray = 0;
192         break;
193     }
194     return DefWindowProcW( hwnd, msg, wparam, lparam );
195 }
196
197 /* add an icon to the standalone tray window */
198 static void add_to_standalone_tray( struct tray_icon *icon )
199 {
200     SIZE size;
201     POINT pos;
202
203     if (!standalone_tray)
204     {
205         static const WCHAR winname[] = {'W','i','n','e',' ','S','y','s','t','e','m',' ','T','r','a','y',0};
206
207         size = get_window_size();
208         standalone_tray = CreateWindowExW( 0, tray_classname, winname, WS_CAPTION | WS_SYSMENU,
209                                            CW_USEDEFAULT, CW_USEDEFAULT, size.cx, size.cy, 0, 0, 0, 0 );
210         if (!standalone_tray) return;
211     }
212
213     icon->display = nb_displayed;
214     pos = get_icon_pos( icon );
215     icon->window = CreateWindowW( icon_classname, NULL, WS_CHILD | WS_VISIBLE,
216                                   pos.x, pos.y, icon_cx, icon_cy, standalone_tray, NULL, NULL, icon );
217     if (!icon->window)
218     {
219         icon->display = -1;
220         return;
221     }
222     create_tooltip( icon );
223
224     nb_displayed++;
225     size = get_window_size();
226     SetWindowPos( standalone_tray, 0, 0, 0, size.cx, size.cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER );
227     if (nb_displayed == 1 && show_systray) ShowWindow( standalone_tray, SW_SHOWNA );
228     TRACE( "added %u now %d icons\n", icon->id, nb_displayed );
229 }
230
231 /* remove an icon from the stand-alone tray */
232 static void remove_from_standalone_tray( struct tray_icon *icon )
233 {
234     struct tray_icon *ptr;
235     POINT pos;
236
237     if (icon->display == -1) return;
238
239     LIST_FOR_EACH_ENTRY( ptr, &icon_list, struct tray_icon, entry )
240     {
241         if (ptr == icon) continue;
242         if (ptr->display < icon->display) continue;
243         ptr->display--;
244         pos = get_icon_pos( ptr );
245         SetWindowPos( ptr->window, 0, pos.x, pos.y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER );
246     }
247     icon->display = -1;
248     if (!--nb_displayed) ShowWindow( standalone_tray, SW_HIDE );
249     TRACE( "removed %u now %d icons\n", icon->id, nb_displayed );
250 }
251
252 /* window procedure for the individual tray icon window */
253 static LRESULT WINAPI tray_icon_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
254 {
255     struct tray_icon *icon = NULL;
256     BOOL ret;
257
258     WINE_TRACE("hwnd=%p, msg=0x%x\n", hwnd, msg);
259
260     /* set the icon data for the window from the data passed into CreateWindow */
261     if (msg == WM_NCCREATE)
262         SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LPARAM)((const CREATESTRUCTW *)lparam)->lpCreateParams);
263
264     icon = (struct tray_icon *) GetWindowLongPtrW(hwnd, GWLP_USERDATA);
265
266     switch (msg)
267     {
268     case WM_CREATE:
269         SetTimer( hwnd, 1, 1000, NULL );
270         break;
271
272     case WM_PAINT:
273         {
274             PAINTSTRUCT ps;
275             RECT rc;
276             HDC hdc;
277             int cx = GetSystemMetrics( SM_CXSMICON );
278             int cy = GetSystemMetrics( SM_CYSMICON );
279
280             hdc = BeginPaint(hwnd, &ps);
281             GetClientRect(hwnd, &rc);
282             TRACE("painting rect %s\n", wine_dbgstr_rect(&rc));
283             DrawIconEx( hdc, (rc.left + rc.right - cx) / 2, (rc.top + rc.bottom - cy) / 2,
284                         icon->image, cx, cy, 0, 0, DI_DEFAULTSIZE|DI_NORMAL );
285             EndPaint(hwnd, &ps);
286             return 0;
287         }
288
289     case WM_MOUSEMOVE:
290     case WM_LBUTTONDOWN:
291     case WM_LBUTTONUP:
292     case WM_RBUTTONDOWN:
293     case WM_RBUTTONUP:
294     case WM_MBUTTONDOWN:
295     case WM_MBUTTONUP:
296     case WM_LBUTTONDBLCLK:
297     case WM_RBUTTONDBLCLK:
298     case WM_MBUTTONDBLCLK:
299         /* notify the owner hwnd of the message */
300         TRACE("relaying 0x%x\n", msg);
301         ret = PostMessageW(icon->owner, icon->callback_message, icon->id, msg);
302         if (!ret && (GetLastError() == ERROR_INVALID_WINDOW_HANDLE))
303         {
304             WARN( "application window was destroyed, removing icon %u\n", icon->id );
305             delete_icon( icon );
306         }
307         return 0;
308
309     case WM_TIMER:
310         if (!IsWindow( icon->owner )) delete_icon( icon );
311         return 0;
312
313     case WM_CLOSE:
314         if (icon->display == -1)
315         {
316             TRACE( "icon %u no longer embedded\n", icon->id );
317             hide_icon( icon );
318             add_to_standalone_tray( icon );
319         }
320         return 0;
321     }
322     return DefWindowProcW( hwnd, msg, wparam, lparam );
323 }
324
325 /* find the X11 window owner the system tray selection */
326 static Window get_systray_selection_owner( Display *display )
327 {
328     Window ret;
329
330     wine_tsx11_lock();
331     ret = XGetSelectionOwner( display, systray_atom );
332     wine_tsx11_unlock();
333     return ret;
334 }
335
336 static BOOL init_systray(void)
337 {
338     static BOOL init_done;
339     WNDCLASSEXW class;
340     Display *display;
341
342     if (root_window != DefaultRootWindow( gdi_display )) return FALSE;
343     if (init_done) return TRUE;
344
345     icon_cx = GetSystemMetrics( SM_CXSMICON ) + 2 * ICON_BORDER;
346     icon_cy = GetSystemMetrics( SM_CYSMICON ) + 2 * ICON_BORDER;
347
348     memset( &class, 0, sizeof(class) );
349     class.cbSize        = sizeof(class);
350     class.lpfnWndProc   = tray_icon_wndproc;
351     class.hIcon         = LoadIconW(0, (LPCWSTR)IDI_WINLOGO);
352     class.hCursor       = LoadCursorW( 0, (LPCWSTR)IDC_ARROW );
353     class.lpszClassName = icon_classname;
354     class.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
355
356     if (!RegisterClassExW( &class ) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
357     {
358         ERR( "Could not register icon tray window class\n" );
359         return FALSE;
360     }
361
362     class.lpfnWndProc   = standalone_tray_wndproc;
363     class.hbrBackground = (HBRUSH)COLOR_WINDOW;
364     class.lpszClassName = tray_classname;
365     class.style         = CS_DBLCLKS;
366
367     if (!RegisterClassExW( &class ) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS)
368     {
369         ERR( "Could not register standalone tray window class\n" );
370         return FALSE;
371     }
372
373     display = thread_init_display();
374     wine_tsx11_lock();
375     if (DefaultScreen( display ) == 0)
376         systray_atom = x11drv_atom(_NET_SYSTEM_TRAY_S0);
377     else
378     {
379         char systray_buffer[29]; /* strlen(_NET_SYSTEM_TRAY_S4294967295)+1 */
380         sprintf( systray_buffer, "_NET_SYSTEM_TRAY_S%u", DefaultScreen( display ) );
381         systray_atom = XInternAtom( display, systray_buffer, False );
382     }
383     XSelectInput( display, root_window, StructureNotifyMask );
384     wine_tsx11_unlock();
385
386     init_done = TRUE;
387     return TRUE;
388 }
389
390 /* dock the given icon with the NETWM system tray */
391 static void dock_systray_icon( Display *display, struct tray_icon *icon, Window systray_window )
392 {
393     struct x11drv_win_data *data;
394     XEvent ev;
395     XSetWindowAttributes attr;
396
397     icon->window = CreateWindowW( icon_classname, NULL, WS_CLIPSIBLINGS | WS_POPUP,
398                                   CW_USEDEFAULT, CW_USEDEFAULT, icon_cx, icon_cy,
399                                   NULL, NULL, NULL, icon );
400     if (!icon->window) return;
401
402     if (!(data = X11DRV_get_win_data( icon->window )) &&
403         !(data = X11DRV_create_win_data( icon->window ))) return;
404
405     TRACE( "icon window %p/%lx managed %u\n", data->hwnd, data->whole_window, data->managed );
406
407     make_window_embedded( display, data );
408     create_tooltip( icon );
409     ShowWindow( icon->window, SW_SHOWNA );
410
411     /* send the docking request message */
412     ev.xclient.type = ClientMessage;
413     ev.xclient.window = systray_window;
414     ev.xclient.message_type = x11drv_atom( _NET_SYSTEM_TRAY_OPCODE );
415     ev.xclient.format = 32;
416     ev.xclient.data.l[0] = CurrentTime;
417     ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK;
418     ev.xclient.data.l[2] = data->whole_window;
419     ev.xclient.data.l[3] = 0;
420     ev.xclient.data.l[4] = 0;
421     wine_tsx11_lock();
422     XSendEvent( display, systray_window, False, NoEventMask, &ev );
423     attr.background_pixmap = ParentRelative;
424     attr.bit_gravity = ForgetGravity;
425     XChangeWindowAttributes( display, data->whole_window, CWBackPixmap | CWBitGravity, &attr );
426     XChangeWindowAttributes( display, data->client_window, CWBackPixmap | CWBitGravity, &attr );
427     wine_tsx11_unlock();
428 }
429
430 /* dock systray windows again with the new owner */
431 void change_systray_owner( Display *display, Window systray_window )
432 {
433     struct tray_icon *icon;
434
435     TRACE( "new owner %lx\n", systray_window );
436     LIST_FOR_EACH_ENTRY( icon, &icon_list, struct tray_icon, entry )
437     {
438         if (icon->display == -1) continue;
439         hide_icon( icon );
440         dock_systray_icon( display, icon, systray_window );
441     }
442 }
443
444 /* hide a tray icon */
445 static BOOL hide_icon( struct tray_icon *icon )
446 {
447     struct x11drv_win_data *data;
448
449     TRACE( "id=0x%x, hwnd=%p\n", icon->id, icon->owner );
450
451     if (!icon->window) return TRUE;  /* already hidden */
452
453     /* make sure we don't try to unmap it, it confuses some systray docks */
454     if ((data = X11DRV_get_win_data( icon->window )) && data->embedded) data->mapped = FALSE;
455
456     DestroyWindow(icon->window);
457     DestroyWindow(icon->tooltip);
458     icon->window = 0;
459     icon->tooltip = 0;
460     remove_from_standalone_tray( icon );
461     return TRUE;
462 }
463
464 /* make the icon visible */
465 static BOOL show_icon( struct tray_icon *icon )
466 {
467     Window systray_window;
468     Display *display = thread_init_display();
469
470     TRACE( "id=0x%x, hwnd=%p\n", icon->id, icon->owner );
471
472     if (icon->window) return TRUE;  /* already shown */
473
474     if ((systray_window = get_systray_selection_owner( display )))
475         dock_systray_icon( display, icon, systray_window );
476     else
477         add_to_standalone_tray( icon );
478
479     return TRUE;
480 }
481
482 /* Modifies an existing icon record */
483 static BOOL modify_icon( struct tray_icon *icon, NOTIFYICONDATAW *nid )
484 {
485     TRACE( "id=0x%x hwnd=%p flags=%x\n", nid->uID, nid->hWnd, nid->uFlags );
486
487     if ((nid->uFlags & NIF_STATE) && (nid->dwStateMask & NIS_HIDDEN))
488     {
489         if (nid->dwState & NIS_HIDDEN) hide_icon( icon );
490         else show_icon( icon );
491     }
492
493     if (nid->uFlags & NIF_ICON)
494     {
495         if (icon->image) DestroyIcon(icon->image);
496         icon->image = CopyIcon(nid->hIcon);
497         if (icon->window)
498         {
499             if (icon->display != -1) InvalidateRect( icon->window, NULL, TRUE );
500             else
501             {
502                 struct x11drv_win_data *data = X11DRV_get_win_data( icon->window );
503                 if (data) XClearArea( gdi_display, data->client_window, 0, 0, 0, 0, True );
504             }
505         }
506     }
507
508     if (nid->uFlags & NIF_MESSAGE)
509     {
510         icon->callback_message = nid->uCallbackMessage;
511     }
512     if (nid->uFlags & NIF_TIP)
513     {
514         lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR));
515         icon->tiptitle[0] = 0;
516         if (icon->tooltip) update_tooltip_text(icon);
517     }
518     if (nid->uFlags & NIF_INFO && nid->cbSize >= NOTIFYICONDATAA_V2_SIZE)
519     {
520         lstrcpynW(icon->tiptext, nid->szInfo, sizeof(icon->tiptext)/sizeof(WCHAR));
521         lstrcpynW(icon->tiptitle, nid->szInfoTitle, sizeof(icon->tiptitle)/sizeof(WCHAR));
522         if (icon->tooltip) update_tooltip_text(icon);
523     }
524     return TRUE;
525 }
526
527 /* Adds a new icon record to the list */
528 static BOOL add_icon(NOTIFYICONDATAW *nid)
529 {
530     struct tray_icon  *icon;
531
532     WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd);
533
534     if ((icon = get_icon(nid->hWnd, nid->uID)))
535     {
536         WINE_WARN("duplicate tray icon add, buggy app?\n");
537         return FALSE;
538     }
539
540     if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon))))
541     {
542         WINE_ERR("out of memory\n");
543         return FALSE;
544     }
545
546     ZeroMemory(icon, sizeof(struct tray_icon));
547     icon->id     = nid->uID;
548     icon->owner  = nid->hWnd;
549     icon->display = -1;
550
551     list_add_tail(&icon_list, &icon->entry);
552
553     /* if hidden state is specified, modify_icon will take care of it */
554     if (!((nid->uFlags & NIF_STATE) && (nid->dwStateMask & NIS_HIDDEN)))
555         show_icon( icon );
556
557     return modify_icon( icon, nid );
558 }
559
560 /* delete tray icon window and icon structure */
561 static BOOL delete_icon( struct tray_icon *icon )
562 {
563     hide_icon( icon );
564     list_remove( &icon->entry );
565     DestroyIcon( icon->image );
566     HeapFree( GetProcessHeap(), 0, icon );
567     return TRUE;
568 }
569
570
571 /***********************************************************************
572  *              wine_notify_icon   (X11DRV.@)
573  *
574  * Driver-side implementation of Shell_NotifyIcon.
575  */
576 int CDECL wine_notify_icon( DWORD msg, NOTIFYICONDATAW *data )
577 {
578     BOOL ret = FALSE;
579     struct tray_icon *icon;
580
581     switch (msg)
582     {
583     case NIM_ADD:
584         if (!init_systray()) return -1;  /* fall back to default handling */
585         ret = add_icon( data );
586         break;
587     case NIM_DELETE:
588         if ((icon = get_icon( data->hWnd, data->uID ))) ret = delete_icon( icon );
589         break;
590     case NIM_MODIFY:
591         if ((icon = get_icon( data->hWnd, data->uID ))) ret = modify_icon( icon, data );
592         break;
593     default:
594         FIXME( "unhandled tray message: %u\n", msg );
595         break;
596     }
597     return ret;
598 }