2 * X11 system tray management
4 * Copyright (C) 2004 Mike Hearn, for CodeWeavers
5 * Copyright (C) 2005 Robert Shearman
6 * Copyright (C) 2008 Alexandre Julliard
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.
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.
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
43 #include "wine/list.h"
44 #include "wine/debug.h"
46 WINE_DEFAULT_DEBUG_CHANNEL(systray
);
48 /* an individual systray icon */
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 WCHAR tiptext
[256]; /* Tooltip text. If empty => tooltip disabled */
59 WCHAR tiptitle
[64]; /* Tooltip title for ballon style tooltips. If empty => tooltip is not balloon style. */
62 static struct list icon_list
= LIST_INIT( icon_list
);
64 static const WCHAR tray_classname
[] = {'_','_','w','i','n','e','x','1','1','_','t','r','a','y','_','w','i','n','d','o','w',0};
66 static BOOL
delete_icon( struct tray_icon
*icon
);
68 #define SYSTEM_TRAY_REQUEST_DOCK 0
69 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
70 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
74 /* retrieves icon record by owner window and ID */
75 static struct tray_icon
*get_icon(HWND owner
, UINT id
)
77 struct tray_icon
*this;
79 LIST_FOR_EACH_ENTRY( this, &icon_list
, struct tray_icon
, entry
)
80 if ((this->id
== id
) && (this->owner
== owner
)) return this;
84 /* create tooltip window for icon */
85 static void create_tooltip(struct tray_icon
*icon
)
87 static BOOL tooltips_initialized
= FALSE
;
89 if (!tooltips_initialized
)
91 INITCOMMONCONTROLSEX init_tooltip
;
93 init_tooltip
.dwSize
= sizeof(INITCOMMONCONTROLSEX
);
94 init_tooltip
.dwICC
= ICC_TAB_CLASSES
;
96 InitCommonControlsEx(&init_tooltip
);
97 tooltips_initialized
= TRUE
;
99 if (icon
->tiptitle
[0] != 0)
101 icon
->tooltip
= CreateWindowExW( WS_EX_TOPMOST
, TOOLTIPS_CLASSW
, NULL
,
102 WS_POPUP
| TTS_ALWAYSTIP
| TTS_BALLOON
,
103 CW_USEDEFAULT
, CW_USEDEFAULT
,
104 CW_USEDEFAULT
, CW_USEDEFAULT
,
105 icon
->window
, NULL
, NULL
, NULL
);
109 icon
->tooltip
= CreateWindowExW( WS_EX_TOPMOST
, TOOLTIPS_CLASSW
, NULL
,
110 WS_POPUP
| TTS_ALWAYSTIP
,
111 CW_USEDEFAULT
, CW_USEDEFAULT
,
112 CW_USEDEFAULT
, CW_USEDEFAULT
,
113 icon
->window
, NULL
, NULL
, NULL
);
118 ZeroMemory(&ti
, sizeof(ti
));
119 ti
.cbSize
= sizeof(TTTOOLINFOW
);
120 ti
.uFlags
= TTF_SUBCLASS
| TTF_IDISHWND
;
121 ti
.hwnd
= icon
->window
;
122 ti
.uId
= (UINT_PTR
)icon
->window
;
123 ti
.lpszText
= icon
->tiptext
;
124 SendMessageW(icon
->tooltip
, TTM_ADDTOOLW
, 0, (LPARAM
)&ti
);
128 /* synchronize tooltip text with tooltip window */
129 static void update_tooltip_text(struct tray_icon
*icon
)
133 ZeroMemory(&ti
, sizeof(ti
));
134 ti
.cbSize
= sizeof(TTTOOLINFOW
);
135 ti
.uFlags
= TTF_SUBCLASS
| TTF_IDISHWND
;
136 ti
.hwnd
= icon
->window
;
137 ti
.uId
= (UINT_PTR
)icon
->window
;
138 ti
.lpszText
= icon
->tiptext
;
140 SendMessageW(icon
->tooltip
, TTM_UPDATETIPTEXTW
, 0, (LPARAM
)&ti
);
143 /* window procedure for the tray window */
144 static LRESULT WINAPI
tray_wndproc(HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
146 struct tray_icon
*icon
= NULL
;
149 WINE_TRACE("hwnd=%p, msg=0x%x\n", hwnd
, msg
);
151 /* set the icon data for the window from the data passed into CreateWindow */
152 if (msg
== WM_NCCREATE
)
153 SetWindowLongPtrW(hwnd
, GWLP_USERDATA
, (LPARAM
)((const CREATESTRUCTW
*)lparam
)->lpCreateParams
);
155 icon
= (struct tray_icon
*) GetWindowLongPtrW(hwnd
, GWLP_USERDATA
);
164 int cx
= GetSystemMetrics( SM_CXSMICON
);
165 int cy
= GetSystemMetrics( SM_CYSMICON
);
167 hdc
= BeginPaint(hwnd
, &ps
);
168 GetClientRect(hwnd
, &rc
);
169 TRACE("painting rect %s\n", wine_dbgstr_rect(&rc
));
170 DrawIconEx( hdc
, (rc
.left
+ rc
.right
- cx
) / 2, (rc
.top
+ rc
.bottom
- cy
) / 2,
171 icon
->image
, cx
, cy
, 0, 0, DI_DEFAULTSIZE
|DI_NORMAL
);
183 case WM_LBUTTONDBLCLK
:
184 case WM_RBUTTONDBLCLK
:
185 case WM_MBUTTONDBLCLK
:
186 /* notify the owner hwnd of the message */
187 TRACE("relaying 0x%x\n", msg
);
188 ret
= PostMessageW(icon
->owner
, icon
->callback_message
, (WPARAM
) icon
->id
, (LPARAM
) msg
);
189 if (!ret
&& (GetLastError() == ERROR_INVALID_WINDOW_HANDLE
))
191 WARN( "application window was destroyed, removing icon %u\n", icon
->id
);
197 if (!IsWindow( icon
->owner
)) delete_icon( icon
);
201 return DefWindowProcW(hwnd
, msg
, wparam
, lparam
);
206 /* find the X11 window owner the system tray selection */
207 static Window
get_systray_selection_owner( Display
*display
)
209 static Atom systray_atom
;
212 if (root_window
!= DefaultRootWindow( display
)) return 0;
217 if (DefaultScreen( display
) == 0)
218 systray_atom
= x11drv_atom(_NET_SYSTEM_TRAY_S0
);
221 char systray_buffer
[29]; /* strlen(_NET_SYSTEM_TRAY_S4294967295)+1 */
222 sprintf( systray_buffer
, "_NET_SYSTEM_TRAY_S%u", DefaultScreen( display
) );
223 systray_atom
= XInternAtom( display
, systray_buffer
, False
);
226 ret
= XGetSelectionOwner( display
, systray_atom
);
232 /* dock the given X window with the NETWM system tray */
233 static void dock_systray_window( Display
*display
, HWND hwnd
, Window systray_window
)
235 struct x11drv_win_data
*data
;
237 XSetWindowAttributes attr
;
239 if (!(data
= X11DRV_get_win_data( hwnd
)) &&
240 !(data
= X11DRV_create_win_data( hwnd
))) return;
242 TRACE( "icon window %p/%lx managed %u\n", data
->hwnd
, data
->whole_window
, data
->managed
);
244 make_window_embedded( display
, data
);
246 /* send the docking request message */
247 ev
.xclient
.type
= ClientMessage
;
248 ev
.xclient
.window
= systray_window
;
249 ev
.xclient
.message_type
= x11drv_atom( _NET_SYSTEM_TRAY_OPCODE
);
250 ev
.xclient
.format
= 32;
251 ev
.xclient
.data
.l
[0] = CurrentTime
;
252 ev
.xclient
.data
.l
[1] = SYSTEM_TRAY_REQUEST_DOCK
;
253 ev
.xclient
.data
.l
[2] = data
->whole_window
;
254 ev
.xclient
.data
.l
[3] = 0;
255 ev
.xclient
.data
.l
[4] = 0;
257 XSendEvent( display
, systray_window
, False
, NoEventMask
, &ev
);
258 attr
.background_pixmap
= ParentRelative
;
259 attr
.bit_gravity
= ForgetGravity
;
260 XChangeWindowAttributes( display
, data
->whole_window
, CWBackPixmap
| CWBitGravity
, &attr
);
261 XChangeWindowAttributes( display
, data
->client_window
, CWBackPixmap
| CWBitGravity
, &attr
);
266 /* hide a tray icon */
267 static BOOL
hide_icon( struct tray_icon
*icon
)
269 TRACE( "id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
271 if (!icon
->window
) return TRUE
; /* already hidden */
273 DestroyWindow(icon
->window
);
274 DestroyWindow(icon
->tooltip
);
280 /* make the icon visible */
281 static BOOL
show_icon( struct tray_icon
*icon
)
284 static BOOL class_registered
;
285 Window systray_window
;
286 Display
*display
= thread_display();
288 TRACE( "id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
290 if (icon
->window
) return TRUE
; /* already shown */
292 if (!class_registered
)
296 ZeroMemory( &class, sizeof(class) );
297 class.cbSize
= sizeof(class);
298 class.lpfnWndProc
= tray_wndproc
;
299 class.hCursor
= LoadCursorW( 0, (LPCWSTR
)IDC_ARROW
);
300 class.lpszClassName
= tray_classname
;
301 class.style
= CS_HREDRAW
| CS_VREDRAW
| CS_DBLCLKS
;
303 if (!RegisterClassExW(&class) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS
)
305 WINE_ERR( "Could not register tray window class\n" );
308 class_registered
= TRUE
;
311 if (!(systray_window
= get_systray_selection_owner( display
))) return FALSE
;
315 rect
.right
= GetSystemMetrics( SM_CXSMICON
) + 2*ICON_BORDER
;
316 rect
.bottom
= GetSystemMetrics( SM_CYSMICON
) + 2*ICON_BORDER
;
318 icon
->window
= CreateWindowExW( WS_EX_APPWINDOW
, tray_classname
, NULL
, WS_CLIPSIBLINGS
| WS_POPUP
,
319 CW_USEDEFAULT
, CW_USEDEFAULT
,
320 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
,
321 NULL
, NULL
, NULL
, icon
);
322 create_tooltip( icon
);
323 dock_systray_window( display
, icon
->window
, systray_window
);
324 SetTimer( icon
->window
, 1, 1000, NULL
);
325 ShowWindow( icon
->window
, SW_SHOWNA
);
329 /* Modifies an existing icon record */
330 static BOOL
modify_icon( struct tray_icon
*icon
, NOTIFYICONDATAW
*nid
)
332 TRACE( "id=0x%x hwnd=%p flags=%x\n", nid
->uID
, nid
->hWnd
, nid
->uFlags
);
334 if ((nid
->uFlags
& NIF_STATE
) && (nid
->dwStateMask
& NIS_HIDDEN
))
336 if (nid
->dwState
& NIS_HIDDEN
) hide_icon( icon
);
337 else show_icon( icon
);
340 if (nid
->uFlags
& NIF_ICON
)
342 if (icon
->image
) DestroyIcon(icon
->image
);
343 icon
->image
= CopyIcon(nid
->hIcon
);
346 struct x11drv_win_data
*data
= X11DRV_get_win_data( icon
->window
);
347 if (data
) XClearArea( gdi_display
, data
->client_window
, 0, 0, 0, 0, True
);
351 if (nid
->uFlags
& NIF_MESSAGE
)
353 icon
->callback_message
= nid
->uCallbackMessage
;
355 if (nid
->uFlags
& NIF_TIP
)
357 lstrcpynW(icon
->tiptext
, nid
->szTip
, sizeof(icon
->tiptext
)/sizeof(WCHAR
));
358 icon
->tiptitle
[0] = 0;
359 if (icon
->tooltip
) update_tooltip_text(icon
);
361 if (nid
->uFlags
& NIF_INFO
&& nid
->cbSize
>= NOTIFYICONDATAA_V2_SIZE
)
363 lstrcpynW(icon
->tiptext
, nid
->szInfo
, sizeof(icon
->tiptext
)/sizeof(WCHAR
));
364 lstrcpynW(icon
->tiptitle
, nid
->szInfoTitle
, sizeof(icon
->tiptitle
)/sizeof(WCHAR
));
365 if (icon
->tooltip
) update_tooltip_text(icon
);
370 /* Adds a new icon record to the list */
371 static BOOL
add_icon(NOTIFYICONDATAW
*nid
)
373 struct tray_icon
*icon
;
375 WINE_TRACE("id=0x%x, hwnd=%p\n", nid
->uID
, nid
->hWnd
);
377 if ((icon
= get_icon(nid
->hWnd
, nid
->uID
)))
379 WINE_WARN("duplicate tray icon add, buggy app?\n");
383 if (!(icon
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(*icon
))))
385 WINE_ERR("out of memory\n");
389 ZeroMemory(icon
, sizeof(struct tray_icon
));
391 icon
->owner
= nid
->hWnd
;
393 list_add_tail(&icon_list
, &icon
->entry
);
395 /* if hidden state is specified, modify_icon will take care of it */
396 if (!((nid
->uFlags
& NIF_STATE
) && (nid
->dwStateMask
& NIS_HIDDEN
)))
399 return modify_icon( icon
, nid
);
402 /* delete tray icon window and icon structure */
403 static BOOL
delete_icon( struct tray_icon
*icon
)
406 list_remove( &icon
->entry
);
407 DestroyIcon( icon
->image
);
408 HeapFree( GetProcessHeap(), 0, icon
);
413 /***********************************************************************
414 * wine_notify_icon (X11DRV.@)
416 * Driver-side implementation of Shell_NotifyIcon.
418 int wine_notify_icon( DWORD msg
, NOTIFYICONDATAW
*data
)
421 struct tray_icon
*icon
;
426 if (!get_systray_selection_owner( thread_init_display() ))
427 return -1; /* fall back to default handling */
428 ret
= add_icon( data
);
431 if ((icon
= get_icon( data
->hWnd
, data
->uID
))) ret
= delete_icon( icon
);
434 if ((icon
= get_icon( data
->hWnd
, data
->uID
))) ret
= modify_icon( icon
, data
);
437 FIXME( "unhandled tray message: %u\n", msg
);