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
36 #include "wine/list.h"
37 #include "wine/debug.h"
39 WINE_DEFAULT_DEBUG_CHANNEL(systray
);
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
;
56 static struct list icon_list
= LIST_INIT(icon_list
);
59 static BOOL
delete_icon(struct tray_icon
*icon
);
62 /***********************************************************************
65 * Delete all systray icons owned by a given window.
67 static void cleanup_icons(HWND hwnd
)
69 struct tray_icon
*icon
, *next
;
71 LIST_FOR_EACH_ENTRY_SAFE(icon
, next
, &icon_list
, struct tray_icon
, entry
)
72 if (icon
->owner
== hwnd
) delete_icon(icon
);
76 /***********************************************************************
79 * Retrieves an icon record by owner window and ID.
81 static struct tray_icon
*get_icon(HWND owner
, UINT id
)
83 struct tray_icon
*this;
85 LIST_FOR_EACH_ENTRY(this, &icon_list
, struct tray_icon
, entry
)
86 if ((this->id
== id
) && (this->owner
== owner
)) return this;
91 /***********************************************************************
94 * Modifies an existing tray icon and updates its status item as needed.
96 static BOOL
modify_icon(struct tray_icon
*icon
, NOTIFYICONDATAW
*nid
)
98 BOOL update_image
= FALSE
, update_tooltip
= FALSE
;
100 TRACE("hwnd %p id 0x%x flags %x\n", nid
->hWnd
, nid
->uID
, nid
->uFlags
);
102 if (nid
->uFlags
& NIF_STATE
)
104 DWORD changed
= (icon
->state
^ nid
->dwState
) & nid
->dwStateMask
;
105 icon
->state
= (icon
->state
& ~nid
->dwStateMask
) | (nid
->dwState
& nid
->dwStateMask
);
106 if (changed
& NIS_HIDDEN
)
108 if (icon
->state
& NIS_HIDDEN
)
110 if (icon
->status_item
)
112 TRACE("destroying status item %p\n", icon
->status_item
);
113 macdrv_destroy_status_item(icon
->status_item
);
114 icon
->status_item
= NULL
;
119 if (!icon
->status_item
)
121 struct macdrv_thread_data
*thread_data
= macdrv_init_thread_data();
123 icon
->status_item
= macdrv_create_status_item(thread_data
->queue
);
124 if (icon
->status_item
)
126 TRACE("created status item %p\n", icon
->status_item
);
131 update_tooltip
= TRUE
;
134 WARN("failed to create status item\n");
140 if (nid
->uFlags
& NIF_ICON
)
142 if (icon
->image
) NtUserDestroyCursor(icon
->image
, 0);
143 icon
->image
= CopyImage(nid
->hIcon
, IMAGE_ICON
, 0, 0, 0);
144 if (icon
->status_item
)
148 if (nid
->uFlags
& NIF_MESSAGE
)
150 icon
->callback_message
= nid
->uCallbackMessage
;
152 if (nid
->uFlags
& NIF_TIP
)
154 lstrcpynW(icon
->tiptext
, nid
->szTip
, ARRAY_SIZE(icon
->tiptext
));
155 if (icon
->status_item
)
156 update_tooltip
= TRUE
;
161 CGImageRef cgimage
= NULL
;
163 cgimage
= create_cgimage_from_icon(icon
->image
, 0, 0);
164 macdrv_set_status_item_image(icon
->status_item
, cgimage
);
165 CGImageRelease(cgimage
);
172 TRACE("setting tooltip text for status item %p to %s\n", icon
->status_item
,
173 debugstr_w(icon
->tiptext
));
174 s
= CFStringCreateWithCharacters(NULL
, (UniChar
*)icon
->tiptext
,
175 lstrlenW(icon
->tiptext
));
176 macdrv_set_status_item_tooltip(icon
->status_item
, s
);
184 /***********************************************************************
187 * Creates a new tray icon structure and adds it to the list.
189 static BOOL
add_icon(NOTIFYICONDATAW
*nid
)
191 NOTIFYICONDATAW new_nid
;
192 struct tray_icon
*icon
;
194 TRACE("hwnd %p id 0x%x\n", nid
->hWnd
, nid
->uID
);
196 if ((icon
= get_icon(nid
->hWnd
, nid
->uID
)))
198 WARN("duplicate tray icon add, buggy app?\n");
202 if (!(icon
= calloc(1, sizeof(*icon
))))
204 ERR("out of memory\n");
209 icon
->owner
= nid
->hWnd
;
210 icon
->state
= NIS_HIDDEN
;
212 list_add_tail(&icon_list
, &icon
->entry
);
214 if (!(nid
->uFlags
& NIF_STATE
) || !(nid
->dwStateMask
& NIS_HIDDEN
))
217 new_nid
.uFlags
|= NIF_STATE
;
218 new_nid
.dwState
&= ~NIS_HIDDEN
;
219 new_nid
.dwStateMask
|= NIS_HIDDEN
;
222 return modify_icon(icon
, nid
);
226 /***********************************************************************
229 * Destroy tray icon status item and delete structure.
231 static BOOL
delete_icon(struct tray_icon
*icon
)
233 TRACE("hwnd %p id 0x%x\n", icon
->owner
, icon
->id
);
235 if (icon
->status_item
)
237 TRACE("destroying status item %p\n", icon
->status_item
);
238 macdrv_destroy_status_item(icon
->status_item
);
240 list_remove(&icon
->entry
);
241 NtUserDestroyCursor(icon
->image
, 0);
247 /***********************************************************************
248 * wine_notify_icon (MACDRV.@)
250 * Driver-side implementation of Shell_NotifyIcon.
252 NTSTATUS
macdrv_notify_icon(void *arg
)
254 struct notify_icon_params
*params
= arg
;
255 NOTIFYICONDATAW
*data
= params
->data
;
257 struct tray_icon
*icon
;
262 ret
= add_icon(data
);
265 if ((icon
= get_icon(data
->hWnd
, data
->uID
))) ret
= delete_icon(icon
);
268 if ((icon
= get_icon(data
->hWnd
, data
->uID
))) ret
= modify_icon(icon
, data
);
270 case 0xdead: /* Wine extension: owner window has died */
271 cleanup_icons(data
->hWnd
);
274 if ((icon
= get_icon(data
->hWnd
, data
->uID
)))
276 icon
->version
= data
->uVersion
;
281 FIXME("unhandled tray message: %u\n", params
->msg
);
287 static BOOL
notify_owner(struct tray_icon
*icon
, UINT msg
, int x
, int y
)
289 WPARAM wp
= icon
->id
;
292 if (icon
->version
>= NOTIFYICON_VERSION_4
)
294 wp
= MAKEWPARAM(x
, y
);
295 lp
= MAKELPARAM(msg
, icon
->id
);
298 TRACE("posting msg 0x%04x to hwnd %p id 0x%x\n", msg
, icon
->owner
, icon
->id
);
299 if (!NtUserMessageCall(icon
->owner
, icon
->callback_message
, wp
, lp
,
300 0, NtUserSendNotifyMessage
, FALSE
) &&
301 (RtlGetLastWin32Error() == ERROR_INVALID_WINDOW_HANDLE
))
303 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);
310 /***********************************************************************
311 * macdrv_status_item_mouse_button
313 * Handle STATUS_ITEM_MOUSE_BUTTON events.
315 void macdrv_status_item_mouse_button(const macdrv_event
*event
)
317 struct tray_icon
*icon
;
319 TRACE("item %p button %d down %d count %d pos %d,%d\n", event
->status_item_mouse_button
.item
,
320 event
->status_item_mouse_button
.button
, event
->status_item_mouse_button
.down
,
321 event
->status_item_mouse_button
.count
, event
->status_item_mouse_button
.x
,
322 event
->status_item_mouse_button
.y
);
324 LIST_FOR_EACH_ENTRY(icon
, &icon_list
, struct tray_icon
, entry
)
326 if (icon
->status_item
== event
->status_item_mouse_button
.item
)
330 switch (event
->status_item_mouse_button
.button
)
332 case 0: msg
= WM_LBUTTONDOWN
; break;
333 case 1: msg
= WM_RBUTTONDOWN
; break;
334 case 2: msg
= WM_MBUTTONDOWN
; break;
336 TRACE("ignoring button beyond the third\n");
340 if (!event
->status_item_mouse_button
.down
)
341 msg
+= WM_LBUTTONUP
- WM_LBUTTONDOWN
;
342 else if (event
->status_item_mouse_button
.count
% 2 == 0)
343 msg
+= WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
;
345 if (!send_message(icon
->owner
, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS
, 0, 0) &&
346 RtlGetLastWin32Error() == ERROR_INVALID_WINDOW_HANDLE
)
348 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);
353 if (!notify_owner(icon
, msg
, event
->status_item_mouse_button
.x
, event
->status_item_mouse_button
.y
))
358 if (msg
== WM_LBUTTONUP
)
359 notify_owner(icon
, NIN_SELECT
, event
->status_item_mouse_button
.x
, event
->status_item_mouse_button
.y
);
360 else if (msg
== WM_RBUTTONUP
)
361 notify_owner(icon
, WM_CONTEXTMENU
, event
->status_item_mouse_button
.x
, event
->status_item_mouse_button
.y
);
370 /***********************************************************************
371 * macdrv_status_item_mouse_move
373 * Handle STATUS_ITEM_MOUSE_MOVE events.
375 void macdrv_status_item_mouse_move(const macdrv_event
*event
)
377 struct tray_icon
*icon
;
379 TRACE("item %p pos %d,%d\n", event
->status_item_mouse_move
.item
,
380 event
->status_item_mouse_move
.x
, event
->status_item_mouse_move
.y
);
382 LIST_FOR_EACH_ENTRY(icon
, &icon_list
, struct tray_icon
, entry
)
384 if (icon
->status_item
== event
->status_item_mouse_move
.item
)
386 notify_owner(icon
, WM_MOUSEMOVE
, event
->status_item_mouse_move
.x
, event
->status_item_mouse_move
.y
);