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
[128]; /* Tooltip text. If empty => tooltip disabled */
61 static struct list icon_list
= LIST_INIT( icon_list
);
63 static const WCHAR tray_classname
[] = {'_','_','w','i','n','e','x','1','1','_','t','r','a','y','_','w','i','n','d','o','w',0};
65 static BOOL
delete_icon( struct tray_icon
*icon
);
67 #define SYSTEM_TRAY_REQUEST_DOCK 0
68 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
69 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
73 /* retrieves icon record by owner window and ID */
74 static struct tray_icon
*get_icon(HWND owner
, UINT id
)
76 struct tray_icon
*this;
78 LIST_FOR_EACH_ENTRY( this, &icon_list
, struct tray_icon
, entry
)
79 if ((this->id
== id
) && (this->owner
== owner
)) return this;
83 /* create tooltip window for icon */
84 static void create_tooltip(struct tray_icon
*icon
)
86 static BOOL tooltips_initialized
= FALSE
;
88 if (!tooltips_initialized
)
90 INITCOMMONCONTROLSEX init_tooltip
;
92 init_tooltip
.dwSize
= sizeof(INITCOMMONCONTROLSEX
);
93 init_tooltip
.dwICC
= ICC_TAB_CLASSES
;
95 InitCommonControlsEx(&init_tooltip
);
96 tooltips_initialized
= TRUE
;
99 icon
->tooltip
= CreateWindowExW( WS_EX_TOPMOST
, TOOLTIPS_CLASSW
, NULL
,
100 WS_POPUP
| TTS_ALWAYSTIP
,
101 CW_USEDEFAULT
, CW_USEDEFAULT
,
102 CW_USEDEFAULT
, CW_USEDEFAULT
,
103 icon
->window
, NULL
, NULL
, NULL
);
107 ZeroMemory(&ti
, sizeof(ti
));
108 ti
.cbSize
= sizeof(TTTOOLINFOW
);
109 ti
.uFlags
= TTF_SUBCLASS
| TTF_IDISHWND
;
110 ti
.hwnd
= icon
->window
;
111 ti
.uId
= (UINT_PTR
)icon
->window
;
112 ti
.lpszText
= icon
->tiptext
;
113 SendMessageW(icon
->tooltip
, TTM_ADDTOOLW
, 0, (LPARAM
)&ti
);
117 /* synchronize tooltip text with tooltip window */
118 static void update_tooltip_text(struct tray_icon
*icon
)
122 ZeroMemory(&ti
, sizeof(ti
));
123 ti
.cbSize
= sizeof(TTTOOLINFOW
);
124 ti
.uFlags
= TTF_SUBCLASS
| TTF_IDISHWND
;
125 ti
.hwnd
= icon
->window
;
126 ti
.uId
= (UINT_PTR
)icon
->window
;
127 ti
.lpszText
= icon
->tiptext
;
129 SendMessageW(icon
->tooltip
, TTM_UPDATETIPTEXTW
, 0, (LPARAM
)&ti
);
132 /* window procedure for the tray window */
133 static LRESULT WINAPI
tray_wndproc(HWND hwnd
, UINT msg
, WPARAM wparam
, LPARAM lparam
)
135 struct tray_icon
*icon
= NULL
;
138 WINE_TRACE("hwnd=%p, msg=0x%x\n", hwnd
, msg
);
140 /* set the icon data for the window from the data passed into CreateWindow */
141 if (msg
== WM_NCCREATE
)
142 SetWindowLongPtrW(hwnd
, GWLP_USERDATA
, (LPARAM
)((const CREATESTRUCTW
*)lparam
)->lpCreateParams
);
144 icon
= (struct tray_icon
*) GetWindowLongPtrW(hwnd
, GWLP_USERDATA
);
153 int cx
= GetSystemMetrics( SM_CXSMICON
);
154 int cy
= GetSystemMetrics( SM_CYSMICON
);
156 hdc
= BeginPaint(hwnd
, &ps
);
157 GetClientRect(hwnd
, &rc
);
158 TRACE("painting rect %s\n", wine_dbgstr_rect(&rc
));
159 DrawIconEx( hdc
, (rc
.left
+ rc
.right
- cx
) / 2, (rc
.top
+ rc
.bottom
- cy
) / 2,
160 icon
->image
, cx
, cy
, 0, 0, DI_DEFAULTSIZE
|DI_NORMAL
);
172 case WM_LBUTTONDBLCLK
:
173 case WM_RBUTTONDBLCLK
:
174 case WM_MBUTTONDBLCLK
:
175 /* notify the owner hwnd of the message */
176 TRACE("relaying 0x%x\n", msg
);
177 ret
= PostMessageW(icon
->owner
, icon
->callback_message
, (WPARAM
) icon
->id
, (LPARAM
) msg
);
178 if (!ret
&& (GetLastError() == ERROR_INVALID_WINDOW_HANDLE
))
180 WARN( "application window was destroyed, removing icon %u\n", icon
->id
);
186 if (!IsWindow( icon
->owner
)) delete_icon( icon
);
190 return DefWindowProcW(hwnd
, msg
, wparam
, lparam
);
195 /* find the X11 window owner the system tray selection */
196 static Window
get_systray_selection_owner( Display
*display
)
198 static Atom systray_atom
;
201 if (root_window
!= DefaultRootWindow( display
)) return 0;
206 if (DefaultScreen( display
) == 0)
207 systray_atom
= x11drv_atom(_NET_SYSTEM_TRAY_S0
);
210 char systray_buffer
[29]; /* strlen(_NET_SYSTEM_TRAY_S4294967295)+1 */
211 sprintf( systray_buffer
, "_NET_SYSTEM_TRAY_S%u", DefaultScreen( display
) );
212 systray_atom
= XInternAtom( display
, systray_buffer
, False
);
215 ret
= XGetSelectionOwner( display
, systray_atom
);
221 /* dock the given X window with the NETWM system tray */
222 static void dock_systray_window( HWND hwnd
, Window systray_window
)
224 Display
*display
= thread_display();
225 struct x11drv_win_data
*data
;
227 XSetWindowAttributes attr
;
229 if (!(data
= X11DRV_get_win_data( hwnd
)) &&
230 !(data
= X11DRV_create_win_data( hwnd
))) return;
232 TRACE( "icon window %p/%lx managed %u\n", data
->hwnd
, data
->whole_window
, data
->managed
);
234 make_window_embedded( display
, data
);
236 /* send the docking request message */
237 ev
.xclient
.type
= ClientMessage
;
238 ev
.xclient
.window
= systray_window
;
239 ev
.xclient
.message_type
= x11drv_atom( _NET_SYSTEM_TRAY_OPCODE
);
240 ev
.xclient
.format
= 32;
241 ev
.xclient
.data
.l
[0] = CurrentTime
;
242 ev
.xclient
.data
.l
[1] = SYSTEM_TRAY_REQUEST_DOCK
;
243 ev
.xclient
.data
.l
[2] = data
->whole_window
;
244 ev
.xclient
.data
.l
[3] = 0;
245 ev
.xclient
.data
.l
[4] = 0;
247 XSendEvent( display
, systray_window
, False
, NoEventMask
, &ev
);
248 attr
.background_pixmap
= ParentRelative
;
249 attr
.bit_gravity
= ForgetGravity
;
250 XChangeWindowAttributes( display
, data
->whole_window
, CWBackPixmap
| CWBitGravity
, &attr
);
251 XChangeWindowAttributes( display
, data
->client_window
, CWBackPixmap
| CWBitGravity
, &attr
);
256 /* hide a tray icon */
257 static BOOL
hide_icon( struct tray_icon
*icon
)
259 TRACE( "id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
261 if (!icon
->window
) return TRUE
; /* already hidden */
263 DestroyWindow(icon
->window
);
264 DestroyWindow(icon
->tooltip
);
270 /* make the icon visible */
271 static BOOL
show_icon( struct tray_icon
*icon
)
274 static BOOL class_registered
;
275 Window systray_window
;
277 TRACE( "id=0x%x, hwnd=%p\n", icon
->id
, icon
->owner
);
279 if (icon
->window
) return TRUE
; /* already shown */
281 if (!class_registered
)
285 ZeroMemory( &class, sizeof(class) );
286 class.cbSize
= sizeof(class);
287 class.lpfnWndProc
= tray_wndproc
;
288 class.hCursor
= LoadCursorW( 0, (LPCWSTR
)IDC_ARROW
);
289 class.lpszClassName
= tray_classname
;
290 class.style
= CS_HREDRAW
| CS_VREDRAW
| CS_DBLCLKS
;
292 if (!RegisterClassExW(&class) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS
)
294 WINE_ERR( "Could not register tray window class\n" );
297 class_registered
= TRUE
;
300 if (!(systray_window
= get_systray_selection_owner( thread_display() ))) return FALSE
;
304 rect
.right
= GetSystemMetrics( SM_CXSMICON
) + 2*ICON_BORDER
;
305 rect
.bottom
= GetSystemMetrics( SM_CYSMICON
) + 2*ICON_BORDER
;
307 icon
->window
= CreateWindowExW( WS_EX_APPWINDOW
, tray_classname
, NULL
, WS_CLIPSIBLINGS
| WS_POPUP
,
308 CW_USEDEFAULT
, CW_USEDEFAULT
,
309 rect
.right
- rect
.left
, rect
.bottom
- rect
.top
,
310 NULL
, NULL
, NULL
, icon
);
311 create_tooltip( icon
);
312 dock_systray_window( icon
->window
, systray_window
);
313 SetTimer( icon
->window
, 1, 1000, NULL
);
314 ShowWindow( icon
->window
, SW_SHOWNA
);
318 /* Modifies an existing icon record */
319 static BOOL
modify_icon( struct tray_icon
*icon
, NOTIFYICONDATAW
*nid
)
321 TRACE( "id=0x%x hwnd=%p flags=%x\n", nid
->uID
, nid
->hWnd
, nid
->uFlags
);
323 if ((nid
->uFlags
& NIF_STATE
) && (nid
->dwStateMask
& NIS_HIDDEN
))
325 if (nid
->dwState
& NIS_HIDDEN
) hide_icon( icon
);
326 else show_icon( icon
);
329 if (nid
->uFlags
& NIF_ICON
)
331 if (icon
->image
) DestroyIcon(icon
->image
);
332 icon
->image
= CopyIcon(nid
->hIcon
);
335 struct x11drv_win_data
*data
= X11DRV_get_win_data( icon
->window
);
336 if (data
) XClearArea( gdi_display
, data
->client_window
, 0, 0, 0, 0, True
);
340 if (nid
->uFlags
& NIF_MESSAGE
)
342 icon
->callback_message
= nid
->uCallbackMessage
;
344 if (nid
->uFlags
& NIF_TIP
)
346 lstrcpynW(icon
->tiptext
, nid
->szTip
, sizeof(icon
->tiptext
)/sizeof(WCHAR
));
347 if (icon
->tooltip
) update_tooltip_text(icon
);
349 if (nid
->uFlags
& NIF_INFO
&& nid
->cbSize
>= NOTIFYICONDATAA_V2_SIZE
)
351 FIXME("balloon tip title %s, message %s\n", wine_dbgstr_w(nid
->szInfoTitle
), wine_dbgstr_w(nid
->szInfo
));
356 /* Adds a new icon record to the list */
357 static BOOL
add_icon(NOTIFYICONDATAW
*nid
)
359 struct tray_icon
*icon
;
361 WINE_TRACE("id=0x%x, hwnd=%p\n", nid
->uID
, nid
->hWnd
);
363 if ((icon
= get_icon(nid
->hWnd
, nid
->uID
)))
365 WINE_WARN("duplicate tray icon add, buggy app?\n");
369 if (!(icon
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(*icon
))))
371 WINE_ERR("out of memory\n");
375 ZeroMemory(icon
, sizeof(struct tray_icon
));
377 icon
->owner
= nid
->hWnd
;
379 list_add_tail(&icon_list
, &icon
->entry
);
381 /* if hidden state is specified, modify_icon will take care of it */
382 if (!((nid
->uFlags
& NIF_STATE
) && (nid
->dwStateMask
& NIS_HIDDEN
)))
385 return modify_icon( icon
, nid
);
388 /* delete tray icon window and icon structure */
389 static BOOL
delete_icon( struct tray_icon
*icon
)
392 list_remove( &icon
->entry
);
393 DestroyIcon( icon
->image
);
394 HeapFree( GetProcessHeap(), 0, icon
);
399 /***********************************************************************
400 * wine_notify_icon (X11DRV.@)
402 * Driver-side implementation of Shell_NotifyIcon.
404 BOOL
wine_notify_icon( DWORD msg
, NOTIFYICONDATAW
*data
)
407 struct tray_icon
*icon
;
413 if ((owner
= get_systray_selection_owner( thread_display() ))) ret
= add_icon( data
);
416 if ((icon
= get_icon( data
->hWnd
, data
->uID
))) ret
= delete_icon( icon
);
419 if ((icon
= get_icon( data
->hWnd
, data
->uID
))) ret
= modify_icon( icon
, data
);
422 FIXME( "unhandled tray message: %u\n", msg
);