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 #define CHECK_SYSTRAY_TIMER 1
39 #define CHECK_SYSTRAY_INTERVAL_MS 2000
42 /* an individual systray icon */
46 HWND owner; /* the HWND passed in to the Shell_NotifyIcon call */
47 UINT id; /* the unique id given by the app */
48 UINT callback_message;
49 HICON image; /* the image to render */
50 WCHAR tiptext[128]; /* tooltip text */
51 DWORD state; /* state flags */
52 macdrv_status_item status_item;
55 static struct list icon_list = LIST_INIT(icon_list);
58 static BOOL delete_icon(struct tray_icon *icon);
61 /***********************************************************************
64 * Timer procedure for periodically checking that the systray icons are
65 * still valid (their owning windows still exist).
67 static VOID CALLBACK check_icons(HWND hwnd, UINT msg, UINT_PTR timer, DWORD time)
69 struct tray_icon *icon, *next;
71 LIST_FOR_EACH_ENTRY_SAFE(icon, next, &icon_list, struct tray_icon, entry)
72 if (!IsWindow(icon->owner)) delete_icon(icon);
76 /***********************************************************************
77 * setup_check_icons_timer
79 * Set up a window with a timer to check that tray icons are still valid
80 * (their owning windows still exist).
82 static void setup_check_icons_timer(void)
88 static const WCHAR messageW[] = {'M','e','s','s','a','g','e',0};
91 /* Whether we succeed or not, don't try again. */
94 timer_window = CreateWindowW(messageW, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE,
98 WARN("Could not create systray checking message window\n");
102 if (!SetTimer(timer_window, CHECK_SYSTRAY_TIMER, CHECK_SYSTRAY_INTERVAL_MS, check_icons))
103 WARN("Could not create systray checking timer\n");
108 /***********************************************************************
111 * Retrieves an icon record by owner window and ID.
113 static struct tray_icon *get_icon(HWND owner, UINT id)
115 struct tray_icon *this;
117 LIST_FOR_EACH_ENTRY(this, &icon_list, struct tray_icon, entry)
118 if ((this->id == id) && (this->owner == owner)) return this;
123 /***********************************************************************
126 * Modifies an existing tray icon and updates its status item as needed.
128 static BOOL modify_icon(struct tray_icon *icon, NOTIFYICONDATAW *nid)
130 BOOL update_image = FALSE, update_tooltip = FALSE;
132 TRACE("hwnd %p id 0x%x flags %x\n", nid->hWnd, nid->uID, nid->uFlags);
134 if (nid->uFlags & NIF_STATE)
136 DWORD changed = (icon->state ^ nid->dwState) & nid->dwStateMask;
137 icon->state = (icon->state & ~nid->dwStateMask) | (nid->dwState & nid->dwStateMask);
138 if (changed & NIS_HIDDEN)
140 if (icon->state & NIS_HIDDEN)
142 if (icon->status_item)
144 TRACE("destroying status item %p\n", icon->status_item);
145 macdrv_destroy_status_item(icon->status_item);
146 icon->status_item = NULL;
151 if (!icon->status_item)
153 struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
155 icon->status_item = macdrv_create_status_item(thread_data->queue);
156 if (icon->status_item)
158 TRACE("created status item %p\n", icon->status_item);
162 if (lstrlenW(icon->tiptext))
163 update_tooltip = TRUE;
166 WARN("failed to create status item\n");
172 if (nid->uFlags & NIF_ICON)
174 if (icon->image) DestroyIcon(icon->image);
175 icon->image = CopyIcon(nid->hIcon);
176 if (icon->status_item)
180 if (nid->uFlags & NIF_MESSAGE)
182 icon->callback_message = nid->uCallbackMessage;
184 if (nid->uFlags & NIF_TIP)
186 lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR));
187 if (icon->status_item)
188 update_tooltip = TRUE;
193 CGImageRef cgimage = NULL;
195 cgimage = create_cgimage_from_icon(icon->image, 0, 0);
196 macdrv_set_status_item_image(icon->status_item, cgimage);
197 CGImageRelease(cgimage);
204 TRACE("setting tooltip text for status item %p to %s\n", icon->status_item,
205 debugstr_w(icon->tiptext));
206 s = CFStringCreateWithCharacters(NULL, (UniChar*)icon->tiptext,
207 lstrlenW(icon->tiptext));
208 macdrv_set_status_item_tooltip(icon->status_item, s);
216 /***********************************************************************
219 * Creates a new tray icon structure and adds it to the list.
221 static BOOL add_icon(NOTIFYICONDATAW *nid)
223 NOTIFYICONDATAW new_nid;
224 struct tray_icon *icon;
226 TRACE("hwnd %p id 0x%x\n", nid->hWnd, nid->uID);
228 if ((icon = get_icon(nid->hWnd, nid->uID)))
230 WARN("duplicate tray icon add, buggy app?\n");
234 setup_check_icons_timer();
236 if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon))))
238 ERR("out of memory\n");
243 icon->owner = nid->hWnd;
244 icon->state = NIS_HIDDEN;
246 list_add_tail(&icon_list, &icon->entry);
248 if (!(nid->uFlags & NIF_STATE) || !(nid->dwStateMask & NIS_HIDDEN))
251 new_nid.uFlags |= NIF_STATE;
252 new_nid.dwState &= ~NIS_HIDDEN;
253 new_nid.dwStateMask |= NIS_HIDDEN;
256 return modify_icon(icon, nid);
260 /***********************************************************************
263 * Destroy tray icon status item and delete structure.
265 static BOOL delete_icon(struct tray_icon *icon)
267 TRACE("hwnd %p id 0x%x\n", icon->owner, icon->id);
269 if (icon->status_item)
271 TRACE("destroying status item %p\n", icon->status_item);
272 macdrv_destroy_status_item(icon->status_item);
274 list_remove(&icon->entry);
275 DestroyIcon(icon->image);
276 HeapFree(GetProcessHeap(), 0, icon);
281 /***********************************************************************
282 * wine_notify_icon (MACDRV.@)
284 * Driver-side implementation of Shell_NotifyIcon.
286 int CDECL wine_notify_icon(DWORD msg, NOTIFYICONDATAW *data)
289 struct tray_icon *icon;
294 ret = add_icon(data);
297 if ((icon = get_icon(data->hWnd, data->uID))) ret = delete_icon(icon);
300 if ((icon = get_icon(data->hWnd, data->uID))) ret = modify_icon(icon, data);
303 FIXME("unhandled tray message: %u\n", msg);
310 /***********************************************************************
311 * macdrv_status_item_clicked
313 * Handle STATUS_ITEM_CLICKED events.
315 void macdrv_status_item_clicked(const macdrv_event *event)
317 struct tray_icon *icon;
319 TRACE("item %p count %d\n", event->status_item_clicked.item,
320 event->status_item_clicked.count);
322 LIST_FOR_EACH_ENTRY(icon, &icon_list, struct tray_icon, entry)
324 if (icon->status_item == event->status_item_clicked.item)
328 if (!SendMessageW(icon->owner, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS, 0, 0) &&
329 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
331 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
336 if (event->status_item_clicked.count == 1)
338 down = WM_LBUTTONDOWN;
339 TRACE("posting WM_LBUTTONDOWN to hwnd %p id 0x%x\n", icon->owner, icon->id);
343 down = WM_LBUTTONDBLCLK;
344 TRACE("posting WM_LBUTTONDBLCLK to hwnd %p id 0x%x\n", icon->owner, icon->id);
347 if (!PostMessageW(icon->owner, icon->callback_message, icon->id, down) &&
348 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
350 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
355 TRACE("posting WM_LBUTTONUP to hwnd %p id 0x%x\n", icon->owner, icon->id);
356 if (!PostMessageW(icon->owner, icon->callback_message, icon->id, WM_LBUTTONUP) &&
357 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
359 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);