2 * Copyright (C) 2004 Mike Hearn, for CodeWeavers
3 * Copyright (C) 2005 Robert Shearman
4 * Copyright (C) 2008 Alexandre Julliard
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.
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.
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
23 #define NONAMELESSUNION
24 #define _WIN32_IE 0x500
29 #include <wine/debug.h>
30 #include <wine/list.h>
32 #include "explorer_private.h"
34 WINE_DEFAULT_DEBUG_CHANNEL(systray
);
36 struct notify_data
/* platform-independent format for NOTIFYICONDATA */
41 UINT uCallbackMessage
;
50 WCHAR szInfoTitle
[64];
53 /* data for the icon bitmap */
60 static int (CDECL
*wine_notify_icon
)(DWORD
,NOTIFYICONDATAW
*);
62 /* an individual systray icon, unpacked from the NOTIFYICONDATA and always in unicode */
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 */
81 static struct list icon_list
= LIST_INIT( icon_list
);
82 static HWND tray_window
;
84 static unsigned int alloc_displayed
;
85 static unsigned int nb_displayed
;
86 static struct icon
**displayed
; /* array of currently displayed icons */
88 static BOOL hide_systray
;
89 static int icon_cx
, icon_cy
, tray_width
;
91 static struct icon
*balloon_icon
;
92 static HWND balloon_window
;
94 #define MIN_DISPLAYED 8
97 #define VALID_WIN_TIMER 1
98 #define BALLOON_CREATE_TIMER 2
99 #define BALLOON_SHOW_TIMER 3
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
106 /* Retrieves icon record by owner window and ID */
107 static struct icon
*get_icon(HWND owner
, UINT id
)
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;
118 static RECT
get_icon_rect( struct icon
*icon
)
122 rect
.right
= tray_width
- icon_cx
* icon
->display
;
123 rect
.left
= rect
.right
- icon_cx
;
125 rect
.bottom
= icon_cy
;
129 static void init_common_controls(void)
131 static BOOL initialized
= FALSE
;
135 INITCOMMONCONTROLSEX init_tooltip
;
137 init_tooltip
.dwSize
= sizeof(INITCOMMONCONTROLSEX
);
138 init_tooltip
.dwICC
= ICC_TAB_CLASSES
;
140 InitCommonControlsEx(&init_tooltip
);
145 /* Creates tooltip window for icon. */
146 static void create_tooltip(struct icon
*icon
)
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
);
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
);
165 static void set_balloon_position( struct icon
*icon
)
167 RECT rect
= get_icon_rect( icon
);
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
));
176 static void balloon_create_timer(void)
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
);
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
);
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
);
204 static BOOL
show_balloon( struct icon
*icon
)
206 if (icon
->display
== -1) return FALSE
; /* not displayed */
207 if (!icon
->info_text
[0]) return FALSE
; /* no balloon */
209 SetTimer( tray_window
, BALLOON_CREATE_TIMER
, BALLOON_CREATE_TIMEOUT
, NULL
);
213 static void hide_balloon(void)
215 if (!balloon_icon
) return;
218 KillTimer( tray_window
, BALLOON_SHOW_TIMER
);
219 DestroyWindow( balloon_window
);
222 else KillTimer( tray_window
, BALLOON_CREATE_TIMER
);
226 static void show_next_balloon(void)
230 LIST_FOR_EACH_ENTRY( icon
, &icon_list
, struct icon
, entry
)
231 if (show_balloon( icon
)) break;
234 static void update_balloon( struct icon
*icon
)
236 if (balloon_icon
== icon
)
239 show_balloon( icon
);
241 else if (!balloon_icon
)
243 if (!show_balloon( icon
)) return;
245 if (!balloon_icon
) show_next_balloon();
248 static void balloon_timer(void)
250 if (balloon_icon
) balloon_icon
->info_text
[0] = 0; /* clear text now that balloon has been shown */
255 /* Synchronize tooltip text with tooltip window */
256 static void update_tooltip_text(struct icon
*icon
)
260 ZeroMemory(&ti
, sizeof(ti
));
261 ti
.cbSize
= sizeof(TTTOOLINFOW
);
262 ti
.hwnd
= tray_window
;
263 ti
.lpszText
= icon
->tiptext
;
265 SendMessageW(icon
->tooltip
, TTM_UPDATETIPTEXTW
, 0, (LPARAM
)&ti
);
268 /* synchronize tooltip position with tooltip window */
269 static void update_tooltip_position( struct icon
*icon
)
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
);
281 /* find the icon located at a certain point in the tray window */
282 static struct icon
*icon_from_point( int x
, int y
)
284 if (y
< 0 || y
>= icon_cy
) return NULL
;
286 if (x
< 0 || x
>= icon_cx
* nb_displayed
) return NULL
;
287 return displayed
[x
/ icon_cx
];
290 /* invalidate the portion of the tray window that contains the specified icons */
291 static void invalidate_icons( unsigned int start
, unsigned int end
)
295 rect
.left
= tray_width
- (end
+ 1) * icon_cx
;
297 rect
.right
= tray_width
- start
* icon_cx
;
298 rect
.bottom
= icon_cy
;
299 InvalidateRect( tray_window
, &rect
, TRUE
);
302 /* make an icon visible */
303 static BOOL
show_icon(struct icon
*icon
)
305 WINE_TRACE("id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
307 if (icon
->display
!= -1) return TRUE
; /* already displayed */
309 if (nb_displayed
>= alloc_displayed
)
311 unsigned int new_count
= max( alloc_displayed
* 2, 32 );
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
;
317 alloc_displayed
= new_count
;
320 icon
->display
= nb_displayed
;
321 displayed
[nb_displayed
++] = icon
;
322 update_tooltip_position( icon
);
323 invalidate_icons( nb_displayed
-1, nb_displayed
-1 );
325 if (nb_displayed
== 1 && !hide_systray
) ShowWindow( tray_window
, SW_SHOWNA
);
327 create_tooltip(icon
);
328 update_balloon( icon
);
332 /* make an icon invisible */
333 static BOOL
hide_icon(struct icon
*icon
)
337 WINE_TRACE("id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
339 if (icon
->display
== -1) return TRUE
; /* already hidden */
341 assert( nb_displayed
);
342 for (i
= icon
->display
; i
< nb_displayed
- 1; i
++)
344 displayed
[i
] = displayed
[i
+ 1];
345 displayed
[i
]->display
= i
;
346 update_tooltip_position( displayed
[i
] );
349 invalidate_icons( icon
->display
, nb_displayed
);
352 if (!nb_displayed
) ShowWindow( tray_window
, SW_HIDE
);
354 update_balloon( icon
);
355 update_tooltip_position( icon
);
359 /* Modifies an existing icon record */
360 static BOOL
modify_icon( struct icon
*icon
, NOTIFYICONDATAW
*nid
)
362 WINE_TRACE("id=0x%x, hwnd=%p\n", nid
->uID
, nid
->hWnd
);
364 /* demarshal the request from the NID */
367 WINE_WARN("Invalid icon ID (0x%x) for HWND %p\n", nid
->uID
, nid
->hWnd
);
371 if (nid
->uFlags
& NIF_STATE
)
373 icon
->state
= (icon
->state
& ~nid
->dwStateMask
) | (nid
->dwState
& nid
->dwStateMask
);
376 if (nid
->uFlags
& NIF_ICON
)
378 if (icon
->image
) DestroyIcon(icon
->image
);
379 icon
->image
= CopyIcon(nid
->hIcon
);
380 if (icon
->display
!= -1) invalidate_icons( icon
->display
, icon
->display
);
383 if (nid
->uFlags
& NIF_MESSAGE
)
385 icon
->callback_message
= nid
->uCallbackMessage
;
387 if (nid
->uFlags
& NIF_TIP
)
389 lstrcpynW(icon
->tiptext
, nid
->szTip
, sizeof(icon
->tiptext
)/sizeof(WCHAR
));
390 if (icon
->display
!= -1) update_tooltip_text(icon
);
392 if (nid
->uFlags
& NIF_INFO
&& nid
->cbSize
>= NOTIFYICONDATAA_V2_SIZE
)
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
);
401 if (icon
->state
& NIS_HIDDEN
) hide_icon( icon
);
402 else show_icon( icon
);
406 /* Adds a new icon record to the list */
407 static BOOL
add_icon(NOTIFYICONDATAW
*nid
)
411 WINE_TRACE("id=0x%x, hwnd=%p\n", nid
->uID
, nid
->hWnd
);
413 if ((icon
= get_icon(nid
->hWnd
, nid
->uID
)))
415 WINE_WARN("duplicate tray icon add, buggy app?\n");
419 if (!(icon
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(*icon
))))
421 WINE_ERR("out of memory\n");
425 ZeroMemory(icon
, sizeof(struct icon
));
427 icon
->owner
= nid
->hWnd
;
430 if (list_empty( &icon_list
)) SetTimer( tray_window
, VALID_WIN_TIMER
, VALID_WIN_TIMEOUT
, NULL
);
431 list_add_tail(&icon_list
, &icon
->entry
);
433 return modify_icon( icon
, nid
);
436 /* Deletes tray icon window and icon record */
437 static BOOL
delete_icon(struct 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
);
447 /* cleanup icons belonging to windows that have been destroyed */
448 static void cleanup_destroyed_windows(void)
450 struct icon
*icon
, *next
;
452 LIST_FOR_EACH_ENTRY_SAFE( icon
, next
, &icon_list
, struct icon
, entry
)
453 if (!IsWindow( icon
->owner
)) delete_icon( icon
);
456 static BOOL
handle_incoming(HWND hwndSource
, COPYDATASTRUCT
*cds
)
458 struct icon
*icon
= NULL
;
459 const struct notify_data
*data
;
463 if (cds
->cbData
< sizeof(*data
)) return FALSE
;
466 nid
.cbSize
= sizeof(nid
);
467 nid
.hWnd
= LongToHandle( data
->hWnd
);
469 nid
.uFlags
= data
->uFlags
;
470 nid
.uCallbackMessage
= data
->uCallbackMessage
;
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;
482 /* FIXME: if statement only needed because we don't support interprocess
484 if ((nid
.uFlags
& NIF_ICON
) && cds
->cbData
> sizeof(*data
))
488 const char *buffer
= (const char *)(data
+ 1);
490 cbMaskBits
= (data
->width
* data
->height
+ 15) / 16 * 2;
491 cbColourBits
= (data
->planes
* data
->width
* data
->height
* data
->bpp
+ 15) / 16 * 2;
493 if (cds
->cbData
< sizeof(*data
) + cbMaskBits
+ cbColourBits
)
495 WINE_ERR("buffer underflow\n");
498 nid
.hIcon
= CreateIcon(NULL
, data
->width
, data
->height
, data
->planes
, data
->bpp
,
499 buffer
, buffer
+ cbMaskBits
);
502 /* try forward to x11drv first */
503 if (cds
->dwData
== NIM_ADD
|| !(icon
= get_icon( nid
.hWnd
, nid
.uID
)))
505 if (wine_notify_icon
&& ((ret
= wine_notify_icon( cds
->dwData
, &nid
)) != -1))
507 if (nid
.hIcon
) DestroyIcon( nid
.hIcon
);
516 ret
= add_icon(&nid
);
519 if (icon
) ret
= delete_icon( icon
);
522 if (icon
) ret
= modify_icon( icon
, &nid
);
525 WINE_FIXME("unhandled tray message: %ld\n", cds
->dwData
);
529 if (nid
.hIcon
) DestroyIcon( nid
.hIcon
);
533 static void do_hide_systray(void)
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
);
541 static LRESULT WINAPI
tray_wndproc( HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
546 return handle_incoming((HWND
)wparam
, (COPYDATASTRUCT
*)lparam
);
548 case WM_DISPLAYCHANGE
:
549 if (hide_systray
) do_hide_systray();
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
);
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;
573 hdc
= BeginPaint( hwnd
, &ps
);
574 for (i
= 0; i
< nb_displayed
; i
++)
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
);
582 EndPaint( hwnd
, &ps
);
593 case WM_LBUTTONDBLCLK
:
594 case WM_RBUTTONDBLCLK
:
595 case WM_MBUTTONDBLCLK
:
598 struct icon
*icon
= icon_from_point( (short)LOWORD(lparam
), (short)HIWORD(lparam
) );
601 /* notify the owner hwnd of the message */
602 WINE_TRACE("relaying 0x%x\n", msg
);
605 message
.message
= msg
;
606 message
.wParam
= wparam
;
607 message
.lParam
= lparam
;
608 SendMessageW( icon
->tooltip
, TTM_RELAYEVENT
, 0, (LPARAM
)&message
);
610 if (!PostMessageW( icon
->owner
, icon
->callback_message
, (WPARAM
) icon
->id
, (LPARAM
) msg
) &&
611 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
613 WINE_WARN("application window was destroyed without removing "
614 "notification icon, removing automatically\n");
621 /* don't destroy the tray window, just hide it */
622 ShowWindow( hwnd
, SW_HIDE
);
626 return DefWindowProcW( hwnd
, msg
, wparam
, lparam
);
631 /* this function creates the listener window */
632 void initialize_systray( BOOL using_root
)
636 static const WCHAR classname
[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0};
638 if ((x11drv
= GetModuleHandleA( "winex11.drv" )))
639 wine_notify_icon
= (void *)GetProcAddress( x11drv
, "wine_notify_icon" );
641 icon_cx
= GetSystemMetrics( SM_CXSMICON
) + 2*ICON_BORDER
;
642 icon_cy
= GetSystemMetrics( SM_CYSMICON
) + 2*ICON_BORDER
;
643 hide_systray
= using_root
;
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
;
656 if (!RegisterClassExW(&class))
658 WINE_ERR("Could not register SysTray window class\n");
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 );
668 WINE_ERR("Could not create tray window\n");
672 if (hide_systray
) do_hide_systray();