/* * Mac driver system tray management * * Copyright (C) 2004 Mike Hearn, for CodeWeavers * Copyright (C) 2005 Robert Shearman * Copyright (C) 2008 Alexandre Julliard * Copyright (C) 2012, 2013 Ken Thomases for CodeWeavers Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include "macdrv.h" #include "windef.h" #include "winuser.h" #include "shellapi.h" #include "wine/list.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(systray); /* an individual systray icon */ struct tray_icon { struct list entry; HWND owner; /* the HWND passed in to the Shell_NotifyIcon call */ UINT id; /* the unique id given by the app */ UINT callback_message; HICON image; /* the image to render */ WCHAR tiptext[128]; /* tooltip text */ DWORD state; /* state flags */ macdrv_status_item status_item; UINT version; }; static struct list icon_list = LIST_INIT(icon_list); static BOOL delete_icon(struct tray_icon *icon); /*********************************************************************** * cleanup_icons * * Delete all systray icons owned by a given window. */ static void cleanup_icons(HWND hwnd) { struct tray_icon *icon, *next; LIST_FOR_EACH_ENTRY_SAFE(icon, next, &icon_list, struct tray_icon, entry) if (icon->owner == hwnd) delete_icon(icon); } /*********************************************************************** * get_icon * * Retrieves an icon record by owner window and ID. */ static struct tray_icon *get_icon(HWND owner, UINT id) { struct tray_icon *this; LIST_FOR_EACH_ENTRY(this, &icon_list, struct tray_icon, entry) if ((this->id == id) && (this->owner == owner)) return this; return NULL; } /*********************************************************************** * modify_icon * * Modifies an existing tray icon and updates its status item as needed. */ static BOOL modify_icon(struct tray_icon *icon, NOTIFYICONDATAW *nid) { BOOL update_image = FALSE, update_tooltip = FALSE; TRACE("hwnd %p id 0x%x flags %x\n", nid->hWnd, nid->uID, nid->uFlags); if (nid->uFlags & NIF_STATE) { DWORD changed = (icon->state ^ nid->dwState) & nid->dwStateMask; icon->state = (icon->state & ~nid->dwStateMask) | (nid->dwState & nid->dwStateMask); if (changed & NIS_HIDDEN) { if (icon->state & NIS_HIDDEN) { if (icon->status_item) { TRACE("destroying status item %p\n", icon->status_item); macdrv_destroy_status_item(icon->status_item); icon->status_item = NULL; } } else { if (!icon->status_item) { struct macdrv_thread_data *thread_data = macdrv_init_thread_data(); icon->status_item = macdrv_create_status_item(thread_data->queue); if (icon->status_item) { TRACE("created status item %p\n", icon->status_item); if (icon->image) update_image = TRUE; if (*icon->tiptext) update_tooltip = TRUE; } else WARN("failed to create status item\n"); } } } } if (nid->uFlags & NIF_ICON) { if (icon->image) DestroyIcon(icon->image); icon->image = CopyIcon(nid->hIcon); if (icon->status_item) update_image = TRUE; } if (nid->uFlags & NIF_MESSAGE) { icon->callback_message = nid->uCallbackMessage; } if (nid->uFlags & NIF_TIP) { lstrcpynW(icon->tiptext, nid->szTip, ARRAY_SIZE(icon->tiptext)); if (icon->status_item) update_tooltip = TRUE; } if (update_image) { CGImageRef cgimage = NULL; if (icon->image) cgimage = create_cgimage_from_icon(icon->image, 0, 0); macdrv_set_status_item_image(icon->status_item, cgimage); CGImageRelease(cgimage); } if (update_tooltip) { CFStringRef s; TRACE("setting tooltip text for status item %p to %s\n", icon->status_item, debugstr_w(icon->tiptext)); s = CFStringCreateWithCharacters(NULL, (UniChar*)icon->tiptext, lstrlenW(icon->tiptext)); macdrv_set_status_item_tooltip(icon->status_item, s); CFRelease(s); } return TRUE; } /*********************************************************************** * add_icon * * Creates a new tray icon structure and adds it to the list. */ static BOOL add_icon(NOTIFYICONDATAW *nid) { NOTIFYICONDATAW new_nid; struct tray_icon *icon; TRACE("hwnd %p id 0x%x\n", nid->hWnd, nid->uID); if ((icon = get_icon(nid->hWnd, nid->uID))) { WARN("duplicate tray icon add, buggy app?\n"); return FALSE; } if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon)))) { ERR("out of memory\n"); return FALSE; } icon->id = nid->uID; icon->owner = nid->hWnd; icon->state = NIS_HIDDEN; list_add_tail(&icon_list, &icon->entry); if (!(nid->uFlags & NIF_STATE) || !(nid->dwStateMask & NIS_HIDDEN)) { new_nid = *nid; new_nid.uFlags |= NIF_STATE; new_nid.dwState &= ~NIS_HIDDEN; new_nid.dwStateMask |= NIS_HIDDEN; nid = &new_nid; } return modify_icon(icon, nid); } /*********************************************************************** * delete_icon * * Destroy tray icon status item and delete structure. */ static BOOL delete_icon(struct tray_icon *icon) { TRACE("hwnd %p id 0x%x\n", icon->owner, icon->id); if (icon->status_item) { TRACE("destroying status item %p\n", icon->status_item); macdrv_destroy_status_item(icon->status_item); } list_remove(&icon->entry); DestroyIcon(icon->image); HeapFree(GetProcessHeap(), 0, icon); return TRUE; } /*********************************************************************** * wine_notify_icon (MACDRV.@) * * Driver-side implementation of Shell_NotifyIcon. */ int CDECL wine_notify_icon(DWORD msg, NOTIFYICONDATAW *data) { BOOL ret = FALSE; struct tray_icon *icon; switch (msg) { case NIM_ADD: ret = add_icon(data); break; case NIM_DELETE: if ((icon = get_icon(data->hWnd, data->uID))) ret = delete_icon(icon); break; case NIM_MODIFY: if ((icon = get_icon(data->hWnd, data->uID))) ret = modify_icon(icon, data); break; case 0xdead: /* Wine extension: owner window has died */ cleanup_icons(data->hWnd); break; case NIM_SETVERSION: if ((icon = get_icon(data->hWnd, data->uID))) { icon->version = data->uVersion; ret = TRUE; } break; default: FIXME("unhandled tray message: %u\n", msg); break; } return ret; } static BOOL notify_owner(struct tray_icon *icon, UINT msg, int x, int y) { WPARAM wp = icon->id; LPARAM lp = msg; if (icon->version >= NOTIFY_VERSION_4) { wp = MAKEWPARAM(x, y); lp = MAKELPARAM(msg, icon->id); } TRACE("posting msg 0x%04x to hwnd %p id 0x%x\n", msg, icon->owner, icon->id); if (!PostMessageW(icon->owner, icon->callback_message, wp, lp) && (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)) { WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id); delete_icon(icon); return FALSE; } return TRUE; } /*********************************************************************** * macdrv_status_item_mouse_button * * Handle STATUS_ITEM_MOUSE_BUTTON events. */ void macdrv_status_item_mouse_button(const macdrv_event *event) { struct tray_icon *icon; TRACE("item %p button %d down %d count %d pos %d,%d\n", event->status_item_mouse_button.item, event->status_item_mouse_button.button, event->status_item_mouse_button.down, event->status_item_mouse_button.count, event->status_item_mouse_button.x, event->status_item_mouse_button.y); LIST_FOR_EACH_ENTRY(icon, &icon_list, struct tray_icon, entry) { if (icon->status_item == event->status_item_mouse_button.item) { UINT msg; switch (event->status_item_mouse_button.button) { case 0: msg = WM_LBUTTONDOWN; break; case 1: msg = WM_RBUTTONDOWN; break; case 2: msg = WM_MBUTTONDOWN; break; default: TRACE("ignoring button beyond the third\n"); return; } if (!event->status_item_mouse_button.down) msg += WM_LBUTTONUP - WM_LBUTTONDOWN; else if (event->status_item_mouse_button.count % 2 == 0) msg += WM_LBUTTONDBLCLK - WM_LBUTTONDOWN; if (!SendMessageW(icon->owner, WM_MACDRV_ACTIVATE_ON_FOLLOWING_FOCUS, 0, 0) && GetLastError() == ERROR_INVALID_WINDOW_HANDLE) { WARN("window %p was destroyed, removing icon 0x%x\n", icon->owner, icon->id); delete_icon(icon); return; } if (!notify_owner(icon, msg, event->status_item_mouse_button.x, event->status_item_mouse_button.y)) return; if (icon->version) { if (msg == WM_LBUTTONUP) notify_owner(icon, NIN_SELECT, event->status_item_mouse_button.x, event->status_item_mouse_button.y); else if (msg == WM_RBUTTONUP) notify_owner(icon, WM_CONTEXTMENU, event->status_item_mouse_button.x, event->status_item_mouse_button.y); } break; } } } /*********************************************************************** * macdrv_status_item_mouse_move * * Handle STATUS_ITEM_MOUSE_MOVE events. */ void macdrv_status_item_mouse_move(const macdrv_event *event) { struct tray_icon *icon; TRACE("item %p pos %d,%d\n", event->status_item_mouse_move.item, event->status_item_mouse_move.x, event->status_item_mouse_move.y); LIST_FOR_EACH_ENTRY(icon, &icon_list, struct tray_icon, entry) { if (icon->status_item == event->status_item_mouse_move.item) { notify_owner(icon, WM_MOUSEMOVE, event->status_item_mouse_move.x, event->status_item_mouse_move.y); break; } } }