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_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\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
);
323 LIST_FOR_EACH_ENTRY(icon
, &icon_list
, struct tray_icon
, entry
)
325 if (icon
->status_item
== event
->status_item_mouse_button
.item
)
329 switch (event
->status_item_mouse_button
.button
)
331 case 0: msg
= WM_LBUTTONDOWN
; break;
332 case 1: msg
= WM_RBUTTONDOWN
; break;
333 case 2: msg
= WM_MBUTTONDOWN
; break;
335 TRACE("ignoring button beyond the third\n");
339 if (!event
->status_item_mouse_button
.down
)
340 msg
+= WM_LBUTTONUP
- WM_LBUTTONDOWN
;
341 else if (event
->status_item_mouse_button
.count
% 2 == 0)
342 msg
+= WM_LBUTTONDBLCLK
- WM_LBUTTONDOWN
;
344 if (!SendMessageW(icon
->owner
, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS
, 0, 0) &&
345 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
347 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);
352 TRACE("posting msg 0x%04x to hwnd %p id 0x%x\n", msg
, icon
->owner
, icon
->id
);
353 if (!PostMessageW(icon
->owner
, icon
->callback_message
, icon
->id
, msg
) &&
354 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
356 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);
367 /***********************************************************************
368 * macdrv_status_item_mouse_move
370 * Handle STATUS_ITEM_MOUSE_MOVE events.
372 void macdrv_status_item_mouse_move(const macdrv_event
*event
)
374 struct tray_icon
*icon
;
376 TRACE("item %p\n", event
->status_item_mouse_move
.item
);
378 LIST_FOR_EACH_ENTRY(icon
, &icon_list
, struct tray_icon
, entry
)
380 if (icon
->status_item
== event
->status_item_mouse_move
.item
)
382 if (!PostMessageW(icon
->owner
, icon
->callback_message
, icon
->id
, WM_MOUSEMOVE
) &&
383 GetLastError() == ERROR_INVALID_WINDOW_HANDLE
)
385 WARN("window %p was destroyed, removing icon 0x%x\n", icon
->owner
, icon
->id
);