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
28 #include <wine/debug.h>
29 #include <wine/list.h>
31 #include "explorer_private.h"
33 WINE_DEFAULT_DEBUG_CHANNEL(systray
);
35 struct notify_data
/* platform-independent format for NOTIFYICONDATA */
40 UINT uCallbackMessage
;
49 WCHAR szInfoTitle
[64];
52 /* data for the icon bitmap */
59 static int (CDECL
*wine_notify_icon
)(DWORD
,NOTIFYICONDATAW
*);
61 /* an individual systray icon, unpacked from the NOTIFYICONDATA and always in unicode */
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 */
80 static struct list icon_list
= LIST_INIT( icon_list
);
81 static HWND tray_window
;
83 static unsigned int alloc_displayed
;
84 static unsigned int nb_displayed
;
85 static struct icon
**displayed
; /* array of currently displayed icons */
87 static BOOL hide_systray
;
88 static int icon_cx
, icon_cy
, tray_width
;
90 static struct icon
*balloon_icon
;
91 static HWND balloon_window
;
93 #define MIN_DISPLAYED 8
96 #define VALID_WIN_TIMER 1
97 #define BALLOON_CREATE_TIMER 2
98 #define BALLOON_SHOW_TIMER 3
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
105 /* Retrieves icon record by owner window and ID */
106 static struct icon
*get_icon(HWND owner
, UINT id
)
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;
117 static RECT
get_icon_rect( struct icon
*icon
)
121 rect
.right
= tray_width
- icon_cx
* icon
->display
;
122 rect
.left
= rect
.right
- icon_cx
;
124 rect
.bottom
= icon_cy
;
128 static void init_common_controls(void)
130 static BOOL initialized
= FALSE
;
134 INITCOMMONCONTROLSEX init_tooltip
;
136 init_tooltip
.dwSize
= sizeof(INITCOMMONCONTROLSEX
);
137 init_tooltip
.dwICC
= ICC_TAB_CLASSES
;
139 InitCommonControlsEx(&init_tooltip
);
144 /* Creates tooltip window for icon. */
145 static void create_tooltip(struct icon
*icon
)
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
);
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
);
164 static void set_balloon_position( struct icon
*icon
)
166 RECT rect
= get_icon_rect( icon
);
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
));
175 static void balloon_create_timer(void)
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
);
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
);
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
);
203 static BOOL
show_balloon( struct icon
*icon
)
205 if (icon
->display
== -1) return FALSE
; /* not displayed */
206 if (!icon
->info_text
[0]) return FALSE
; /* no balloon */
208 SetTimer( tray_window
, BALLOON_CREATE_TIMER
, BALLOON_CREATE_TIMEOUT
, NULL
);
212 static void hide_balloon(void)
214 if (!balloon_icon
) return;
217 KillTimer( tray_window
, BALLOON_SHOW_TIMER
);
218 DestroyWindow( balloon_window
);
221 else KillTimer( tray_window
, BALLOON_CREATE_TIMER
);
225 static void show_next_balloon(void)
229 LIST_FOR_EACH_ENTRY( icon
, &icon_list
, struct icon
, entry
)
230 if (show_balloon( icon
)) break;
233 static void update_balloon( struct icon
*icon
)
235 if (balloon_icon
== icon
)
238 show_balloon( icon
);
240 else if (!balloon_icon
)
242 if (!show_balloon( icon
)) return;
244 if (!balloon_icon
) show_next_balloon();
247 static void balloon_timer(void)
249 if (balloon_icon
) balloon_icon
->info_text
[0] = 0; /* clear text now that balloon has been shown */
254 /* Synchronize tooltip text with tooltip window */
255 static void update_tooltip_text(struct icon
*icon
)
259 ZeroMemory(&ti
, sizeof(ti
));
260 ti
.cbSize
= sizeof(TTTOOLINFOW
);
261 ti
.hwnd
= tray_window
;
262 ti
.lpszText
= icon
->tiptext
;
264 SendMessageW(icon
->tooltip
, TTM_UPDATETIPTEXTW
, 0, (LPARAM
)&ti
);
267 /* synchronize tooltip position with tooltip window */
268 static void update_tooltip_position( struct icon
*icon
)
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
);
280 /* find the icon located at a certain point in the tray window */
281 static struct icon
*icon_from_point( int x
, int y
)
283 if (y
< 0 || y
>= icon_cy
) return NULL
;
285 if (x
< 0 || x
>= icon_cx
* nb_displayed
) return NULL
;
286 return displayed
[x
/ icon_cx
];
289 /* invalidate the portion of the tray window that contains the specified icons */
290 static void invalidate_icons( unsigned int start
, unsigned int end
)
294 rect
.left
= tray_width
- (end
+ 1) * icon_cx
;
296 rect
.right
= tray_width
- start
* icon_cx
;
297 rect
.bottom
= icon_cy
;
298 InvalidateRect( tray_window
, &rect
, TRUE
);
301 /* make an icon visible */
302 static BOOL
show_icon(struct icon
*icon
)
304 WINE_TRACE("id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
306 if (icon
->display
!= -1) return TRUE
; /* already displayed */
308 if (nb_displayed
>= alloc_displayed
)
310 unsigned int new_count
= max( alloc_displayed
* 2, 32 );
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
;
316 alloc_displayed
= new_count
;
319 icon
->display
= nb_displayed
;
320 displayed
[nb_displayed
++] = icon
;
321 update_tooltip_position( icon
);
322 invalidate_icons( nb_displayed
-1, nb_displayed
-1 );
324 if (nb_displayed
== 1 && !hide_systray
) ShowWindow( tray_window
, SW_SHOWNA
);
326 create_tooltip(icon
);
327 update_balloon( icon
);
331 /* make an icon invisible */
332 static BOOL
hide_icon(struct icon
*icon
)
336 WINE_TRACE("id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
338 if (icon
->display
== -1) return TRUE
; /* already hidden */
340 assert( nb_displayed
);
341 for (i
= icon
->display
; i
< nb_displayed
- 1; i
++)
343 displayed
[i
] = displayed
[i
+ 1];
344 displayed
[i
]->display
= i
;
345 update_tooltip_position( displayed
[i
] );
348 invalidate_icons( icon
->display
, nb_displayed
);
351 if (!nb_displayed
) ShowWindow( tray_window
, SW_HIDE
);
353 update_balloon( icon
);
354 update_tooltip_position( icon
);
358 /* Modifies an existing icon record */
359 static BOOL
modify_icon( struct icon
*icon
, NOTIFYICONDATAW
*nid
)
361 WINE_TRACE("id=0x%x, hwnd=%p\n", nid
->uID
, nid
->hWnd
);
363 /* demarshal the request from the NID */
366 WINE_WARN("Invalid icon ID (0x%x) for HWND %p\n", nid
->uID
, nid
->hWnd
);
370 if (nid
->uFlags
& NIF_STATE
)
372 icon
->state
= (icon
->state
& ~nid
->dwStateMask
) | (nid
->dwState
& nid
->dwStateMask
);
375 if (nid
->uFlags
& NIF_ICON
)
377 if (icon
->image
) DestroyIcon(icon
->image
);
378 icon
->image
= CopyIcon(nid
->hIcon
);
379 if (icon
->display
!= -1) invalidate_icons( icon
->display
, icon
->display
);
382 if (nid
->uFlags
& NIF_MESSAGE
)
384 icon
->callback_message
= nid
->uCallbackMessage
;
386 if (nid
->uFlags
& NIF_TIP
)
388 lstrcpynW(icon
->tiptext
, nid
->szTip
, sizeof(icon
->tiptext
)/sizeof(WCHAR
));
389 if (icon
->display
!= -1) update_tooltip_text(icon
);
391 if (nid
->uFlags
& NIF_INFO
&& nid
->cbSize
>= NOTIFYICONDATAA_V2_SIZE
)
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
);
400 if (icon
->state
& NIS_HIDDEN
) hide_icon( icon
);
401 else show_icon( icon
);
405 /* Adds a new icon record to the list */
406 static BOOL
add_icon(NOTIFYICONDATAW
*nid
)
410 WINE_TRACE("id=0x%x, hwnd=%p\n", nid
->uID
, nid
->hWnd
);
412 if ((icon
= get_icon(nid
->hWnd
, nid
->uID
)))
414 WINE_WARN("duplicate tray icon add, buggy app?\n");
418 if (!(icon
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(*icon
))))
420 WINE_ERR("out of memory\n");
424 ZeroMemory(icon
, sizeof(struct icon
));
426 icon
->owner
= nid
->hWnd
;
429 if (list_empty( &icon_list
)) SetTimer( tray_window
, VALID_WIN_TIMER
, VALID_WIN_TIMEOUT
, NULL
);
430 list_add_tail(&icon_list
, &icon
->entry
);
432 return modify_icon( icon
, nid
);
435 /* Deletes tray icon window and icon record */
436 static BOOL
delete_icon(struct 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
);
446 /* cleanup icons belonging to windows that have been destroyed */
447 static void cleanup_destroyed_windows(void)
449 struct icon
*icon
, *next
;
451 LIST_FOR_EACH_ENTRY_SAFE( icon
, next
, &icon_list
, struct icon
, entry
)
452 if (!IsWindow( icon
->owner
)) delete_icon( icon
);
455 static BOOL
handle_incoming(HWND hwndSource
, COPYDATASTRUCT
*cds
)
457 struct icon
*icon
= NULL
;
458 const struct notify_data
*data
;
462 if (cds
->cbData
< sizeof(*data
)) return FALSE
;
465 nid
.cbSize
= sizeof(nid
);
466 nid
.hWnd
= LongToHandle( data
->hWnd
);
468 nid
.uFlags
= data
->uFlags
;
469 nid
.uCallbackMessage
= data
->uCallbackMessage
;
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;
481 /* FIXME: if statement only needed because we don't support interprocess
483 if ((nid
.uFlags
& NIF_ICON
) && cds
->cbData
> sizeof(*data
))
487 const char *buffer
= (const char *)(data
+ 1);
489 cbMaskBits
= (data
->width
* data
->height
+ 15) / 16 * 2;
490 cbColourBits
= (data
->planes
* data
->width
* data
->height
* data
->bpp
+ 15) / 16 * 2;
492 if (cds
->cbData
< sizeof(*data
) + cbMaskBits
+ cbColourBits
)
494 WINE_ERR("buffer underflow\n");
497 nid
.hIcon
= CreateIcon(NULL
, data
->width
, data
->height
, data
->planes
, data
->bpp
,
498 buffer
, buffer
+ cbMaskBits
);
501 /* try forward to x11drv first */
502 if (cds
->dwData
== NIM_ADD
|| !(icon
= get_icon( nid
.hWnd
, nid
.uID
)))
504 if (wine_notify_icon
&& ((ret
= wine_notify_icon( cds
->dwData
, &nid
)) != -1))
506 if (nid
.hIcon
) DestroyIcon( nid
.hIcon
);
515 ret
= add_icon(&nid
);
518 if (icon
) ret
= delete_icon( icon
);
521 if (icon
) ret
= modify_icon( icon
, &nid
);
524 WINE_FIXME("unhandled tray message: %ld\n", cds
->dwData
);
528 if (nid
.hIcon
) DestroyIcon( nid
.hIcon
);
532 static void do_hide_systray(void)
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
);
540 static LRESULT WINAPI
tray_wndproc( HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
545 return handle_incoming((HWND
)wparam
, (COPYDATASTRUCT
*)lparam
);
547 case WM_DISPLAYCHANGE
:
548 if (hide_systray
) do_hide_systray();
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
);
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;
572 hdc
= BeginPaint( hwnd
, &ps
);
573 for (i
= 0; i
< nb_displayed
; i
++)
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
);
581 EndPaint( hwnd
, &ps
);
592 case WM_LBUTTONDBLCLK
:
593 case WM_RBUTTONDBLCLK
:
594 case WM_MBUTTONDBLCLK
:
597 struct icon
*icon
= icon_from_point( (short)LOWORD(lparam
), (short)HIWORD(lparam
) );
600 /* notify the owner hwnd of the message */
601 WINE_TRACE("relaying 0x%x\n", msg
);
604 message
.message
= msg
;
605 message
.wParam
= wparam
;
606 message
.lParam
= lparam
;
607 SendMessageW( icon
->tooltip
, TTM_RELAYEVENT
, 0, (LPARAM
)&message
);
609 if (!PostMessageW( icon
->owner
, icon
->callback_message
, (WPARAM
) icon
->id
, (LPARAM
) msg
) &&
610 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
612 WINE_WARN("application window was destroyed without removing "
613 "notification icon, removing automatically\n");
620 /* don't destroy the tray window, just hide it */
621 ShowWindow( hwnd
, SW_HIDE
);
625 return DefWindowProcW( hwnd
, msg
, wparam
, lparam
);
630 /* this function creates the listener window */
631 void initialize_systray( BOOL using_root
)
635 static const WCHAR classname
[] = {'S','h','e','l','l','_','T','r','a','y','W','n','d',0};
637 if ((x11drv
= GetModuleHandleA( "winex11.drv" )))
638 wine_notify_icon
= (void *)GetProcAddress( x11drv
, "wine_notify_icon" );
640 icon_cx
= GetSystemMetrics( SM_CXSMICON
) + 2*ICON_BORDER
;
641 icon_cy
= GetSystemMetrics( SM_CYSMICON
) + 2*ICON_BORDER
;
642 hide_systray
= using_root
;
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
;
655 if (!RegisterClassExW(&class))
657 WINE_ERR("Could not register SysTray window class\n");
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 );
667 WINE_ERR("Could not create tray window\n");
671 if (hide_systray
) do_hide_systray();