winemac.drv: Implement systray version 1-4 notifications.
[wine.git] / dlls / winemac.drv / systray.c
blob9351d7796f5b4227984179463d976b298e12a92f
1 /*
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
24 #include "config.h"
26 #include "macdrv.h"
28 #include "windef.h"
29 #include "winuser.h"
30 #include "shellapi.h"
32 #include "wine/list.h"
33 #include "wine/debug.h"
35 WINE_DEFAULT_DEBUG_CHANNEL(systray);
38 /* an individual systray icon */
39 struct tray_icon
41 struct list entry;
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;
49 UINT version;
52 static struct list icon_list = LIST_INIT(icon_list);
55 static BOOL delete_icon(struct tray_icon *icon);
58 /***********************************************************************
59 * cleanup_icons
61 * Delete all systray icons owned by a given window.
63 static void cleanup_icons(HWND hwnd)
65 struct tray_icon *icon, *next;
67 LIST_FOR_EACH_ENTRY_SAFE(icon, next, &icon_list, struct tray_icon, entry)
68 if (icon->owner == hwnd) delete_icon(icon);
72 /***********************************************************************
73 * get_icon
75 * Retrieves an icon record by owner window and ID.
77 static struct tray_icon *get_icon(HWND owner, UINT id)
79 struct tray_icon *this;
81 LIST_FOR_EACH_ENTRY(this, &icon_list, struct tray_icon, entry)
82 if ((this->id == id) && (this->owner == owner)) return this;
83 return NULL;
87 /***********************************************************************
88 * modify_icon
90 * Modifies an existing tray icon and updates its status item as needed.
92 static BOOL modify_icon(struct tray_icon *icon, NOTIFYICONDATAW *nid)
94 BOOL update_image = FALSE, update_tooltip = FALSE;
96 TRACE("hwnd %p id 0x%x flags %x\n", nid->hWnd, nid->uID, nid->uFlags);
98 if (nid->uFlags & NIF_STATE)
100 DWORD changed = (icon->state ^ nid->dwState) & nid->dwStateMask;
101 icon->state = (icon->state & ~nid->dwStateMask) | (nid->dwState & nid->dwStateMask);
102 if (changed & NIS_HIDDEN)
104 if (icon->state & NIS_HIDDEN)
106 if (icon->status_item)
108 TRACE("destroying status item %p\n", icon->status_item);
109 macdrv_destroy_status_item(icon->status_item);
110 icon->status_item = NULL;
113 else
115 if (!icon->status_item)
117 struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
119 icon->status_item = macdrv_create_status_item(thread_data->queue);
120 if (icon->status_item)
122 TRACE("created status item %p\n", icon->status_item);
124 if (icon->image)
125 update_image = TRUE;
126 if (lstrlenW(icon->tiptext))
127 update_tooltip = TRUE;
129 else
130 WARN("failed to create status item\n");
136 if (nid->uFlags & NIF_ICON)
138 if (icon->image) DestroyIcon(icon->image);
139 icon->image = CopyIcon(nid->hIcon);
140 if (icon->status_item)
141 update_image = TRUE;
144 if (nid->uFlags & NIF_MESSAGE)
146 icon->callback_message = nid->uCallbackMessage;
148 if (nid->uFlags & NIF_TIP)
150 lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR));
151 if (icon->status_item)
152 update_tooltip = TRUE;
155 if (update_image)
157 CGImageRef cgimage = NULL;
158 if (icon->image)
159 cgimage = create_cgimage_from_icon(icon->image, 0, 0);
160 macdrv_set_status_item_image(icon->status_item, cgimage);
161 CGImageRelease(cgimage);
164 if (update_tooltip)
166 CFStringRef s;
168 TRACE("setting tooltip text for status item %p to %s\n", icon->status_item,
169 debugstr_w(icon->tiptext));
170 s = CFStringCreateWithCharacters(NULL, (UniChar*)icon->tiptext,
171 lstrlenW(icon->tiptext));
172 macdrv_set_status_item_tooltip(icon->status_item, s);
173 CFRelease(s);
176 return TRUE;
180 /***********************************************************************
181 * add_icon
183 * Creates a new tray icon structure and adds it to the list.
185 static BOOL add_icon(NOTIFYICONDATAW *nid)
187 NOTIFYICONDATAW new_nid;
188 struct tray_icon *icon;
190 TRACE("hwnd %p id 0x%x\n", nid->hWnd, nid->uID);
192 if ((icon = get_icon(nid->hWnd, nid->uID)))
194 WARN("duplicate tray icon add, buggy app?\n");
195 return FALSE;
198 if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon))))
200 ERR("out of memory\n");
201 return FALSE;
204 icon->id = nid->uID;
205 icon->owner = nid->hWnd;
206 icon->state = NIS_HIDDEN;
208 list_add_tail(&icon_list, &icon->entry);
210 if (!(nid->uFlags & NIF_STATE) || !(nid->dwStateMask & NIS_HIDDEN))
212 new_nid = *nid;
213 new_nid.uFlags |= NIF_STATE;
214 new_nid.dwState &= ~NIS_HIDDEN;
215 new_nid.dwStateMask |= NIS_HIDDEN;
216 nid = &new_nid;
218 return modify_icon(icon, nid);
222 /***********************************************************************
223 * delete_icon
225 * Destroy tray icon status item and delete structure.
227 static BOOL delete_icon(struct tray_icon *icon)
229 TRACE("hwnd %p id 0x%x\n", icon->owner, icon->id);
231 if (icon->status_item)
233 TRACE("destroying status item %p\n", icon->status_item);
234 macdrv_destroy_status_item(icon->status_item);
236 list_remove(&icon->entry);
237 DestroyIcon(icon->image);
238 HeapFree(GetProcessHeap(), 0, icon);
239 return TRUE;
243 /***********************************************************************
244 * wine_notify_icon (MACDRV.@)
246 * Driver-side implementation of Shell_NotifyIcon.
248 int CDECL wine_notify_icon(DWORD msg, NOTIFYICONDATAW *data)
250 BOOL ret = FALSE;
251 struct tray_icon *icon;
253 switch (msg)
255 case NIM_ADD:
256 ret = add_icon(data);
257 break;
258 case NIM_DELETE:
259 if ((icon = get_icon(data->hWnd, data->uID))) ret = delete_icon(icon);
260 break;
261 case NIM_MODIFY:
262 if ((icon = get_icon(data->hWnd, data->uID))) ret = modify_icon(icon, data);
263 break;
264 case 0xdead: /* Wine extension: owner window has died */
265 cleanup_icons(data->hWnd);
266 break;
267 case NIM_SETVERSION:
268 if ((icon = get_icon(data->hWnd, data->uID)))
270 icon->version = data->uVersion;
271 ret = TRUE;
273 break;
274 default:
275 FIXME("unhandled tray message: %u\n", msg);
276 break;
278 return ret;
281 static BOOL notify_owner(struct tray_icon *icon, UINT msg, int x, int y)
283 WPARAM wp = icon->id;
284 LPARAM lp = msg;
286 if (icon->version >= NOTIFY_VERSION_4)
288 wp = MAKEWPARAM(x, y);
289 lp = MAKELPARAM(msg, icon->id);
292 TRACE("posting msg 0x%04x to hwnd %p id 0x%x\n", msg, icon->owner, icon->id);
293 if (!PostMessageW(icon->owner, icon->callback_message, wp, lp) &&
294 (GetLastError() == ERROR_INVALID_WINDOW_HANDLE))
296 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
297 delete_icon(icon);
298 return FALSE;
300 return TRUE;
303 /***********************************************************************
304 * macdrv_status_item_mouse_button
306 * Handle STATUS_ITEM_MOUSE_BUTTON events.
308 void macdrv_status_item_mouse_button(const macdrv_event *event)
310 struct tray_icon *icon;
312 TRACE("item %p button %d down %d count %d pos %d,%d\n", event->status_item_mouse_button.item,
313 event->status_item_mouse_button.button, event->status_item_mouse_button.down,
314 event->status_item_mouse_button.count, event->status_item_mouse_button.x,
315 event->status_item_mouse_button.y);
317 LIST_FOR_EACH_ENTRY(icon, &icon_list, struct tray_icon, entry)
319 if (icon->status_item == event->status_item_mouse_button.item)
321 UINT msg;
323 switch (event->status_item_mouse_button.button)
325 case 0: msg = WM_LBUTTONDOWN; break;
326 case 1: msg = WM_RBUTTONDOWN; break;
327 case 2: msg = WM_MBUTTONDOWN; break;
328 default:
329 TRACE("ignoring button beyond the third\n");
330 return;
333 if (!event->status_item_mouse_button.down)
334 msg += WM_LBUTTONUP - WM_LBUTTONDOWN;
335 else if (event->status_item_mouse_button.count % 2 == 0)
336 msg += WM_LBUTTONDBLCLK - WM_LBUTTONDOWN;
338 if (!SendMessageW(icon->owner, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS, 0, 0) &&
339 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
341 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
342 delete_icon(icon);
343 return;
346 if (!notify_owner(icon, msg, event->status_item_mouse_button.x, event->status_item_mouse_button.y))
347 return;
349 if (icon->version)
351 if (msg == WM_LBUTTONUP)
352 notify_owner(icon, NIN_SELECT, event->status_item_mouse_button.x, event->status_item_mouse_button.y);
353 else if (msg == WM_RBUTTONUP)
354 notify_owner(icon, WM_CONTEXTMENU, event->status_item_mouse_button.x, event->status_item_mouse_button.y);
357 break;
363 /***********************************************************************
364 * macdrv_status_item_mouse_move
366 * Handle STATUS_ITEM_MOUSE_MOVE events.
368 void macdrv_status_item_mouse_move(const macdrv_event *event)
370 struct tray_icon *icon;
372 TRACE("item %p pos %d,%d\n", event->status_item_mouse_move.item,
373 event->status_item_mouse_move.x, event->status_item_mouse_move.y);
375 LIST_FOR_EACH_ENTRY(icon, &icon_list, struct tray_icon, entry)
377 if (icon->status_item == event->status_item_mouse_move.item)
379 notify_owner(icon, WM_MOUSEMOVE, event->status_item_mouse_move.x, event->status_item_mouse_move.y);
380 break;