2 * Mac driver system tray management
4 * Copyright (C) 2004 Mike Hearn, for CodeWeavers
5 * Copyright (C) 2005 Robert Shearman
6 * Copyright (C) 2008 Alexandre Julliard
7 * Copyright (C) 2012, 2013 Ken Thomases for CodeWeavers Inc.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public
11 * License as published by the Free Software Foundation; either
12 * version 2.1 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
32 #include "wine/list.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(systray
);
38 /* an individual systray icon */
42 HWND owner
; /* the HWND passed in to the Shell_NotifyIcon call */
43 UINT id
; /* the unique id given by the app */
44 UINT callback_message
;
45 HICON image
; /* the image to render */
46 WCHAR tiptext
[128]; /* tooltip text */
47 DWORD state
; /* state flags */
48 macdrv_status_item status_item
;
51 static struct list icon_list
= LIST_INIT(icon_list
);
54 static BOOL
delete_icon(struct tray_icon
*icon
);
57 /***********************************************************************
60 * Delete all systray icons owned by a given window.
62 static void cleanup_icons(HWND hwnd
)
64 struct tray_icon
*icon
, *next
;
66 LIST_FOR_EACH_ENTRY_SAFE(icon
, next
, &icon_list
, struct tray_icon
, entry
)
67 if (icon
->owner
== hwnd
) delete_icon(icon
);
71 /***********************************************************************
74 * Retrieves an icon record by owner window and ID.
76 static struct tray_icon
*get_icon(HWND owner
, UINT id
)
78 struct tray_icon
*this;
80 LIST_FOR_EACH_ENTRY(this, &icon_list
, struct tray_icon
, entry
)
81 if ((this->id
== id
) && (this->owner
== owner
)) return this;
86 /***********************************************************************
89 * Modifies an existing tray icon and updates its status item as needed.
91 static BOOL
modify_icon(struct tray_icon
*icon
, NOTIFYICONDATAW
*nid
)
93 BOOL update_image
= FALSE
, update_tooltip
= FALSE
;
95 TRACE("hwnd %p id 0x%x flags %x\n", nid
->hWnd
, nid
->uID
, nid
->uFlags
);
97 if (nid
->uFlags
& NIF_STATE
)
99 DWORD changed
= (icon
->state
^ nid
->dwState
) & nid
->dwStateMask
;
100 icon
->state
= (icon
->state
& ~nid
->dwStateMask
) | (nid
->dwState
& nid
->dwStateMask
);
101 if (changed
& NIS_HIDDEN
)
103 if (icon
->state
& NIS_HIDDEN
)
105 if (icon
->status_item
)
107 TRACE("destroying status item %p\n", icon
->status_item
);
108 macdrv_destroy_status_item(icon
->status_item
);
109 icon
->status_item
= NULL
;
114 if (!icon
->status_item
)
116 struct macdrv_thread_data
*thread_data
= macdrv_init_thread_data();
118 icon
->status_item
= macdrv_create_status_item(thread_data
->queue
);
119 if (icon
->status_item
)
121 TRACE("created status item %p\n", icon
->status_item
);
125 if (lstrlenW(icon
->tiptext
))
126 update_tooltip
= TRUE
;
129 WARN("failed to create status item\n");
135 if (nid
->uFlags
& NIF_ICON
)
137 if (icon
->image
) DestroyIcon(icon
->image
);
138 icon
->image
= CopyIcon(nid
->hIcon
);
139 if (icon
->status_item
)
143 if (nid
->uFlags
& NIF_MESSAGE
)
145 icon
->callback_message
= nid
->uCallbackMessage
;
147 if (nid
->uFlags
& NIF_TIP
)
149 lstrcpynW(icon
->tiptext
, nid
->szTip
, sizeof(icon
->tiptext
)/sizeof(WCHAR
));
150 if (icon
->status_item
)
151 update_tooltip
= TRUE
;
156 CGImageRef cgimage
= NULL
;
158 cgimage
= create_cgimage_from_icon(icon
->image
, 0, 0);
159 macdrv_set_status_item_image(icon
->status_item
, cgimage
);
160 CGImageRelease(cgimage
);
167 TRACE("setting tooltip text for status item %p to %s\n", icon
->status_item
,
168 debugstr_w(icon
->tiptext
));
169 s
= CFStringCreateWithCharacters(NULL
, (UniChar
*)icon
->tiptext
,
170 lstrlenW(icon
->tiptext
));
171 macdrv_set_status_item_tooltip(icon
->status_item
, s
);
179 /***********************************************************************
182 * Creates a new tray icon structure and adds it to the list.
184 static BOOL
add_icon(NOTIFYICONDATAW
*nid
)
186 NOTIFYICONDATAW new_nid
;
187 struct tray_icon
*icon
;
189 TRACE("hwnd %p id 0x%x\n", nid
->hWnd
, nid
->uID
);
191 if ((icon
= get_icon(nid
->hWnd
, nid
->uID
)))
193 WARN("duplicate tray icon add, buggy app?\n");
197 if (!(icon
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, sizeof(*icon
))))
199 ERR("out of memory\n");
204 icon
->owner
= nid
->hWnd
;
205 icon
->state
= NIS_HIDDEN
;
207 list_add_tail(&icon_list
, &icon
->entry
);
209 if (!(nid
->uFlags
& NIF_STATE
) || !(nid
->dwStateMask
& NIS_HIDDEN
))
212 new_nid
.uFlags
|= NIF_STATE
;
213 new_nid
.dwState
&= ~NIS_HIDDEN
;
214 new_nid
.dwStateMask
|= NIS_HIDDEN
;
217 return modify_icon(icon
, nid
);
221 /***********************************************************************
224 * Destroy tray icon status item and delete structure.
226 static BOOL
delete_icon(struct tray_icon
*icon
)
228 TRACE("hwnd %p id 0x%x\n", icon
->owner
, icon
->id
);
230 if (icon
->status_item
)
232 TRACE("destroying status item %p\n", icon
->status_item
);
233 macdrv_destroy_status_item(icon
->status_item
);
235 list_remove(&icon
->entry
);
236 DestroyIcon(icon
->image
);
237 HeapFree(GetProcessHeap(), 0, icon
);
242 /***********************************************************************
243 * wine_notify_icon (MACDRV.@)
245 * Driver-side implementation of Shell_NotifyIcon.
247 int CDECL
wine_notify_icon(DWORD msg
, NOTIFYICONDATAW
*data
)
250 struct tray_icon
*icon
;
255 ret
= add_icon(data
);
258 if ((icon
= get_icon(data
->hWnd
, data
->uID
))) ret
= delete_icon(icon
);
261 if ((icon
= get_icon(data
->hWnd
, data
->uID
))) ret
= modify_icon(icon
, data
);
263 case 0xdead: /* Wine extension: owner window has died */
264 cleanup_icons(data
->hWnd
);
267 FIXME("unhandled tray message: %u\n", msg
);
274 /***********************************************************************
275 * macdrv_status_item_mouse_button
277 * Handle STATUS_ITEM_MOUSE_BUTTON events.
279 void macdrv_status_item_mouse_button(const macdrv_event
*event
)
281 struct tray_icon
*icon
;
283 TRACE("item %p button %d down %d count %d\n", event
->status_item_mouse_button
.item
,
284 event
->status_item_mouse_button
.button
, event
->status_item_mouse_button
.down
,
285 event
->status_item_mouse_button
.count
);
287 LIST_FOR_EACH_ENTRY(icon
, &icon_list
, struct tray_icon
, entry
)
289 if (icon
->status_item
== event
->status_item_mouse_button
.item
)
293 switch (event
->status_item_mouse_button
.button
)
295 case 0: msg
= WM_LBUTTONDOWN
; break;
296 case 1: msg
= WM_RBUTTONDOWN
; break;
297 case 2: msg
= WM_MBUTTONDOWN
; break;
299 TRACE("ignoring button beyond the third\n");
303 if (!event
->status_item_mouse_button
.down
)
304 msg
+= WM_LBUTTONUP
- WM_LBUTTONDOWN
;
305 else if (event
->status_item_mouse_button
.count
% 2 == 0)
306 msg
+= WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
;
308 if (!SendMessageW(icon
->owner
, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS
, 0, 0) &&
309 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
311 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);
316 TRACE("posting msg 0x%04x to hwnd %p id 0x%x\n", msg
, icon
->owner
, icon
->id
);
317 if (!PostMessageW(icon
->owner
, icon
->callback_message
, icon
->id
, msg
) &&
318 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
320 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);
331 /***********************************************************************
332 * macdrv_status_item_mouse_move
334 * Handle STATUS_ITEM_MOUSE_MOVE events.
336 void macdrv_status_item_mouse_move(const macdrv_event
*event
)
338 struct tray_icon
*icon
;
340 TRACE("item %p\n", event
->status_item_mouse_move
.item
);
342 LIST_FOR_EACH_ENTRY(icon
, &icon_list
, struct tray_icon
, entry
)
344 if (icon
->status_item
== event
->status_item_mouse_move
.item
)
346 if (!PostMessageW(icon
->owner
, icon
->callback_message
, icon
->id
, WM_MOUSEMOVE
) &&
347 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
349 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);