Release 1.5.29.
[wine] / dlls / winemac.drv / systray.c
1 /*
2  * Mac driver system tray management
3  *
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.
8  *
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.
13  *
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.
18  *
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
22  */
23
24 #include "config.h"
25
26 #include "macdrv.h"
27
28 #include "windef.h"
29 #include "winuser.h"
30 #include "shellapi.h"
31
32 #include "wine/list.h"
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(systray);
36
37
38 #define CHECK_SYSTRAY_TIMER         1
39 #define CHECK_SYSTRAY_INTERVAL_MS   2000
40
41
42 /* an individual systray icon */
43 struct tray_icon
44 {
45     struct list         entry;
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;
53 };
54
55 static struct list icon_list = LIST_INIT(icon_list);
56
57
58 static BOOL delete_icon(struct tray_icon *icon);
59
60
61 /***********************************************************************
62  *              check_icons
63  *
64  * Timer procedure for periodically checking that the systray icons are
65  * still valid (their owning windows still exist).
66  */
67 static VOID CALLBACK check_icons(HWND hwnd, UINT msg, UINT_PTR timer, DWORD time)
68 {
69     struct tray_icon *icon, *next;
70
71     LIST_FOR_EACH_ENTRY_SAFE(icon, next, &icon_list, struct tray_icon, entry)
72         if (!IsWindow(icon->owner)) delete_icon(icon);
73 }
74
75
76 /***********************************************************************
77  *              setup_check_icons_timer
78  *
79  * Set up a window with a timer to check that tray icons are still valid
80  * (their owning windows still exist).
81  */
82 static void setup_check_icons_timer(void)
83 {
84     static BOOL done;
85
86     if (!done)
87     {
88         static const WCHAR messageW[] = {'M','e','s','s','a','g','e',0};
89         HWND timer_window;
90
91         /* Whether we succeed or not, don't try again. */
92         done = TRUE;
93
94         timer_window = CreateWindowW(messageW, NULL, 0, 0, 0, 0, 0, HWND_MESSAGE,
95                                      NULL, NULL, NULL);
96         if (!timer_window)
97         {
98             WARN("Could not create systray checking message window\n");
99             return;
100         }
101
102         if (!SetTimer(timer_window, CHECK_SYSTRAY_TIMER, CHECK_SYSTRAY_INTERVAL_MS, check_icons))
103             WARN("Could not create systray checking timer\n");
104     }
105 }
106
107
108 /***********************************************************************
109  *              get_icon
110  *
111  * Retrieves an icon record by owner window and ID.
112  */
113 static struct tray_icon *get_icon(HWND owner, UINT id)
114 {
115     struct tray_icon *this;
116
117     LIST_FOR_EACH_ENTRY(this, &icon_list, struct tray_icon, entry)
118         if ((this->id == id) && (this->owner == owner)) return this;
119     return NULL;
120 }
121
122
123 /***********************************************************************
124  *              modify_icon
125  *
126  * Modifies an existing tray icon and updates its status item as needed.
127  */
128 static BOOL modify_icon(struct tray_icon *icon, NOTIFYICONDATAW *nid)
129 {
130     BOOL update_image = FALSE, update_tooltip = FALSE;
131
132     TRACE("hwnd %p id 0x%x flags %x\n", nid->hWnd, nid->uID, nid->uFlags);
133
134     if (nid->uFlags & NIF_STATE)
135     {
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)
139         {
140             if (icon->state & NIS_HIDDEN)
141             {
142                 if (icon->status_item)
143                 {
144                     TRACE("destroying status item %p\n", icon->status_item);
145                     macdrv_destroy_status_item(icon->status_item);
146                     icon->status_item = NULL;
147                 }
148             }
149             else
150             {
151                 if (!icon->status_item)
152                 {
153                     struct macdrv_thread_data *thread_data = macdrv_init_thread_data();
154
155                     icon->status_item = macdrv_create_status_item(thread_data->queue);
156                     if (icon->status_item)
157                     {
158                         TRACE("created status item %p\n", icon->status_item);
159
160                         if (icon->image)
161                             update_image = TRUE;
162                         if (lstrlenW(icon->tiptext))
163                             update_tooltip = TRUE;
164                     }
165                     else
166                         WARN("failed to create status item\n");
167                 }
168             }
169         }
170     }
171
172     if (nid->uFlags & NIF_ICON)
173     {
174         if (icon->image) DestroyIcon(icon->image);
175         icon->image = CopyIcon(nid->hIcon);
176         if (icon->status_item)
177             update_image = TRUE;
178     }
179
180     if (nid->uFlags & NIF_MESSAGE)
181     {
182         icon->callback_message = nid->uCallbackMessage;
183     }
184     if (nid->uFlags & NIF_TIP)
185     {
186         lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR));
187         if (icon->status_item)
188             update_tooltip = TRUE;
189     }
190
191     if (update_image)
192     {
193         CGImageRef cgimage = NULL;
194         if (icon->image)
195             cgimage = create_cgimage_from_icon(icon->image, 0, 0);
196         macdrv_set_status_item_image(icon->status_item, cgimage);
197         CGImageRelease(cgimage);
198     }
199
200     if (update_tooltip)
201     {
202         CFStringRef s;
203
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);
209         CFRelease(s);
210     }
211
212     return TRUE;
213 }
214
215
216 /***********************************************************************
217  *              add_icon
218  *
219  * Creates a new tray icon structure and adds it to the list.
220  */
221 static BOOL add_icon(NOTIFYICONDATAW *nid)
222 {
223     NOTIFYICONDATAW new_nid;
224     struct tray_icon *icon;
225
226     TRACE("hwnd %p id 0x%x\n", nid->hWnd, nid->uID);
227
228     if ((icon = get_icon(nid->hWnd, nid->uID)))
229     {
230         WARN("duplicate tray icon add, buggy app?\n");
231         return FALSE;
232     }
233
234     setup_check_icons_timer();
235
236     if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon))))
237     {
238         ERR("out of memory\n");
239         return FALSE;
240     }
241
242     icon->id     = nid->uID;
243     icon->owner  = nid->hWnd;
244     icon->state  = NIS_HIDDEN;
245
246     list_add_tail(&icon_list, &icon->entry);
247
248     if (!(nid->uFlags & NIF_STATE) || !(nid->dwStateMask & NIS_HIDDEN))
249     {
250         new_nid = *nid;
251         new_nid.uFlags |= NIF_STATE;
252         new_nid.dwState     &= ~NIS_HIDDEN;
253         new_nid.dwStateMask |= NIS_HIDDEN;
254         nid = &new_nid;
255     }
256     return modify_icon(icon, nid);
257 }
258
259
260 /***********************************************************************
261  *              delete_icon
262  *
263  * Destroy tray icon status item and delete structure.
264  */
265 static BOOL delete_icon(struct tray_icon *icon)
266 {
267     TRACE("hwnd %p id 0x%x\n", icon->owner, icon->id);
268
269     if (icon->status_item)
270     {
271         TRACE("destroying status item %p\n", icon->status_item);
272         macdrv_destroy_status_item(icon->status_item);
273     }
274     list_remove(&icon->entry);
275     DestroyIcon(icon->image);
276     HeapFree(GetProcessHeap(), 0, icon);
277     return TRUE;
278 }
279
280
281 /***********************************************************************
282  *              wine_notify_icon   (MACDRV.@)
283  *
284  * Driver-side implementation of Shell_NotifyIcon.
285  */
286 int CDECL wine_notify_icon(DWORD msg, NOTIFYICONDATAW *data)
287 {
288     BOOL ret = FALSE;
289     struct tray_icon *icon;
290
291     switch (msg)
292     {
293     case NIM_ADD:
294         ret = add_icon(data);
295         break;
296     case NIM_DELETE:
297         if ((icon = get_icon(data->hWnd, data->uID))) ret = delete_icon(icon);
298         break;
299     case NIM_MODIFY:
300         if ((icon = get_icon(data->hWnd, data->uID))) ret = modify_icon(icon, data);
301         break;
302     default:
303         FIXME("unhandled tray message: %u\n", msg);
304         break;
305     }
306     return ret;
307 }
308
309
310 /***********************************************************************
311  *              macdrv_status_item_clicked
312  *
313  * Handle STATUS_ITEM_CLICKED events.
314  */
315 void macdrv_status_item_clicked(const macdrv_event *event)
316 {
317     struct tray_icon *icon;
318
319     TRACE("item %p count %d\n", event->status_item_clicked.item,
320           event->status_item_clicked.count);
321
322     LIST_FOR_EACH_ENTRY(icon, &icon_list, struct tray_icon, entry)
323     {
324         if (icon->status_item == event->status_item_clicked.item)
325         {
326             UINT down;
327
328             if (!SendMessageW(icon->owner, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS, 0, 0) &&
329                 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
330             {
331                 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
332                 delete_icon(icon);
333                 return;
334             }
335
336             if (event->status_item_clicked.count == 1)
337             {
338                 down = WM_LBUTTONDOWN;
339                 TRACE("posting WM_LBUTTONDOWN to hwnd %p id 0x%x\n", icon->owner, icon->id);
340             }
341             else
342             {
343                 down = WM_LBUTTONDBLCLK;
344                 TRACE("posting WM_LBUTTONDBLCLK to hwnd %p id 0x%x\n", icon->owner, icon->id);
345             }
346
347             if (!PostMessageW(icon->owner, icon->callback_message, icon->id, down) &&
348                 GetLastError() == ERROR_INVALID_WINDOW_HANDLE)
349             {
350                 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
351                 delete_icon(icon);
352                 return;
353             }
354
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)
358             {
359                 WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id);
360                 delete_icon(icon);
361             }
362
363             break;
364         }
365     }
366 }