/* * Copyright (C) 2004 Mike Hearn, for CodeWeavers * Copyright (C) 2005 Robert Shearman * * 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 */ /* There are two types of window involved here. The first is the * listener window. This is like the taskbar in Windows. It doesn't * ever appear on-screen in our implementation, instead we create * individual mini "adaptor" windows which are docked by the native * systray host. * * In future for those who don't have a systray we could make the * listener window more clever so it can draw itself like the Windows * tray area does (with a clock and stuff). */ #include #define UNICODE #define _WIN32_IE 0x500 #include #include #include #include #include "explorer_private.h" WINE_DEFAULT_DEBUG_CHANNEL(systray); #define IS_OPTION_FALSE(ch) \ ((ch) == 'n' || (ch) == 'N' || (ch) == 'f' || (ch) == 'F' || (ch) == '0') static const WCHAR adaptor_classname[] = /* Adaptor */ {'A','d','a','p','t','o','r',0}; /* tray state */ struct tray { HWND window; struct list icons; }; /* an individual systray icon, unpacked from the NOTIFYICONDATA and always in unicode */ struct icon { struct list entry; HICON image; /* the image to render */ HWND owner; /* the HWND passed in to the Shell_NotifyIcon call */ HWND window; /* the adaptor window */ HWND tooltip; /* Icon tooltip */ UINT id; /* the unique id given by the app */ UINT callback_message; }; static struct tray tray; static BOOL hide_systray; /* adaptor code */ #define ICON_SIZE GetSystemMetrics(SM_CXSMICON) /* space around icon (forces icon to center of KDE systray area) */ #define ICON_BORDER 4 static LRESULT WINAPI adaptor_wndproc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam) { struct icon *icon = NULL; BOOL ret; WINE_TRACE("hwnd=%p, msg=0x%x\n", window, msg); /* set the icon data for the window from the data passed into CreateWindow */ if (msg == WM_NCCREATE) SetWindowLongPtrW(window, GWLP_USERDATA, (LPARAM)((const CREATESTRUCT *)lparam)->lpCreateParams); icon = (struct icon *) GetWindowLongPtr(window, GWLP_USERDATA); switch (msg) { case WM_PAINT: { RECT rc; int top; PAINTSTRUCT ps; HDC hdc; WINE_TRACE("painting\n"); hdc = BeginPaint(window, &ps); GetClientRect(window, &rc); /* calculate top so we can deal with arbitrary sized trays */ top = ((rc.bottom-rc.top)/2) - ((ICON_SIZE)/2); DrawIconEx(hdc, (ICON_BORDER/2), top, icon->image, ICON_SIZE, ICON_SIZE, 0, 0, DI_DEFAULTSIZE|DI_NORMAL); EndPaint(window, &ps); break; } case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK: /* notify the owner hwnd of the message */ WINE_TRACE("relaying 0x%x\n", msg); ret = PostMessage(icon->owner, icon->callback_message, (WPARAM) icon->id, (LPARAM) msg); if (!ret && (GetLastError() == ERROR_INVALID_HANDLE)) { WINE_WARN("application window was destroyed without removing " "notification icon, removing automatically\n"); DestroyWindow(window); } break; case WM_NCDESTROY: SetWindowLongPtr(window, GWLP_USERDATA, 0); list_remove(&icon->entry); DestroyIcon(icon->image); HeapFree(GetProcessHeap(), 0, icon); break; default: return DefWindowProc(window, msg, wparam, lparam); } return 0; } /* listener code */ static struct icon *get_icon(HWND owner, UINT id) { struct icon *this; /* search for the icon */ LIST_FOR_EACH_ENTRY( this, &tray.icons, struct icon, entry ) if ((this->id == id) && (this->owner == owner)) return this; return NULL; } static void set_tooltip(struct icon *icon, WCHAR *szTip, BOOL modify) { TTTOOLINFOW ti; ti.cbSize = sizeof(TTTOOLINFOW); ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND; ti.hwnd = icon->window; ti.hinst = 0; ti.uId = (UINT_PTR)icon->window; ti.lpszText = szTip; ti.lParam = 0; ti.lpReserved = NULL; if (modify) SendMessageW(icon->tooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti); else SendMessageW(icon->tooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti); } static void modify_icon(NOTIFYICONDATAW *nid, BOOL modify_tooltip) { struct icon *icon; WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd); /* demarshal the request from the NID */ icon = get_icon(nid->hWnd, nid->uID); if (!icon) { WINE_WARN("Invalid icon ID (0x%x) for HWND %p\n", nid->uID, nid->hWnd); return; } if (nid->uFlags & NIF_ICON) { if (icon->image) DestroyIcon(icon->image); icon->image = CopyIcon(nid->hIcon); RedrawWindow(icon->window, NULL, NULL, RDW_ERASE | RDW_INVALIDATE | RDW_UPDATENOW); } if (nid->uFlags & NIF_MESSAGE) { icon->callback_message = nid->uCallbackMessage; } if (nid->uFlags & NIF_TIP) { set_tooltip(icon, nid->szTip, modify_tooltip); } if (nid->uFlags & NIF_INFO && nid->cbSize >= NOTIFYICONDATAA_V2_SIZE) { WINE_FIXME("balloon tip title %s, message %s\n", wine_dbgstr_w(nid->szInfoTitle), wine_dbgstr_w(nid->szInfo)); } } static void add_icon(NOTIFYICONDATAW *nid) { RECT rect; struct icon *icon; static const WCHAR adaptor_windowname[] = /* Wine System Tray Adaptor */ {'W','i','n','e',' ','S','y','s','t','e','m',' ','T','r','a','y',' ','A','d','a','p','t','o','r',0}; static BOOL tooltps_initialized = FALSE; WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd); if ((icon = get_icon(nid->hWnd, nid->uID))) { WINE_WARN("duplicate tray icon add, buggy app?\n"); return; } if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon)))) { WINE_ERR("out of memory\n"); return; } icon->id = nid->uID; icon->owner = nid->hWnd; icon->image = NULL; rect.left = 0; rect.top = 0; rect.right = GetSystemMetrics(SM_CXSMICON) + ICON_BORDER; rect.bottom = GetSystemMetrics(SM_CYSMICON) + ICON_BORDER; AdjustWindowRect(&rect, WS_CLIPSIBLINGS | WS_CAPTION, FALSE); /* create the adaptor window */ icon->window = CreateWindowEx(WS_EX_TRAYWINDOW, adaptor_classname, adaptor_windowname, WS_CLIPSIBLINGS | WS_CAPTION, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, NULL, icon); if (!hide_systray) ShowWindow(icon->window, SW_SHOWNA); /* create icon tooltip */ /* Register tooltip classes if this is the first icon */ if (!tooltps_initialized) { INITCOMMONCONTROLSEX init_tooltip; init_tooltip.dwSize = sizeof(INITCOMMONCONTROLSEX); init_tooltip.dwICC = ICC_TAB_CLASSES; InitCommonControlsEx(&init_tooltip); tooltps_initialized = TRUE; } icon->tooltip = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, icon->window, NULL, NULL, NULL); list_add_tail(&tray.icons, &icon->entry); modify_icon(nid, FALSE); } static void delete_icon(const NOTIFYICONDATAW *nid) { struct icon *icon = get_icon(nid->hWnd, nid->uID); WINE_TRACE("id=0x%x, hwnd=%p\n", nid->uID, nid->hWnd); if (!icon) { WINE_ERR("invalid tray icon ID specified: %u\n", nid->uID); return; } DestroyWindow(icon->tooltip); DestroyWindow(icon->window); } static void handle_incoming(HWND hwndSource, COPYDATASTRUCT *cds) { NOTIFYICONDATAW nid; DWORD cbSize; if (cds->cbData < NOTIFYICONDATAW_V1_SIZE) return; cbSize = ((PNOTIFYICONDATA)cds->lpData)->cbSize; if (cbSize < NOTIFYICONDATAW_V1_SIZE) return; ZeroMemory(&nid, sizeof(nid)); memcpy(&nid, cds->lpData, min(sizeof(nid), cbSize)); /* FIXME: if statement only needed because we don't support interprocess * icon handles */ if ((nid.uFlags & NIF_ICON) && (cds->cbData >= nid.cbSize + 2 * sizeof(BITMAP))) { LONG cbMaskBits; LONG cbColourBits; BITMAP bmMask; BITMAP bmColour; const char *buffer = cds->lpData; buffer += nid.cbSize; memcpy(&bmMask, buffer, sizeof(bmMask)); buffer += sizeof(bmMask); memcpy(&bmColour, buffer, sizeof(bmColour)); buffer += sizeof(bmColour); cbMaskBits = (bmMask.bmPlanes * bmMask.bmWidth * bmMask.bmHeight * bmMask.bmBitsPixel) / 8; cbColourBits = (bmColour.bmPlanes * bmColour.bmWidth * bmColour.bmHeight * bmColour.bmBitsPixel) / 8; if (cds->cbData < nid.cbSize + 2 * sizeof(BITMAP) + cbMaskBits + cbColourBits) { WINE_ERR("buffer underflow\n"); return; } /* sanity check */ if ((bmColour.bmWidth != bmMask.bmWidth) || (bmColour.bmHeight != bmMask.bmHeight)) { WINE_ERR("colour and mask bitmaps aren't consistent\n"); return; } nid.hIcon = CreateIcon(NULL, bmColour.bmWidth, bmColour.bmHeight, bmColour.bmPlanes, bmColour.bmBitsPixel, buffer, buffer + cbMaskBits); } switch (cds->dwData) { case NIM_ADD: add_icon(&nid); break; case NIM_DELETE: delete_icon(&nid); break; case NIM_MODIFY: modify_icon(&nid, TRUE); break; default: WINE_FIXME("unhandled tray message: %ld\n", cds->dwData); break; } /* FIXME: if statement only needed because we don't support interprocess * icon handles */ if (nid.uFlags & NIF_ICON) DestroyIcon(nid.hIcon); } static LRESULT WINAPI listener_wndproc(HWND window, UINT msg, WPARAM wparam, LPARAM lparam) { if (msg == WM_COPYDATA) handle_incoming((HWND)wparam, (COPYDATASTRUCT *)lparam); return DefWindowProc(window, msg, wparam, lparam); } static BOOL is_systray_hidden(void) { const WCHAR show_systray_keyname[] = {'S','o','f','t','w','a','r','e','\\','W','i','n','e','\\', 'X','1','1',' ','D','r','i','v','e','r',0}; const WCHAR show_systray_valuename[] = {'S','h','o','w','S','y','s','t','r','a','y',0}; HKEY hkey; BOOL ret = FALSE; /* @@ Wine registry key: HKCU\Software\Wine\X11 Driver */ if (RegOpenKeyW(HKEY_CURRENT_USER, show_systray_keyname, &hkey) == ERROR_SUCCESS) { WCHAR value[10]; DWORD type, size = sizeof(value); if (RegQueryValueExW(hkey, show_systray_valuename, 0, &type, (LPBYTE)&value, &size) == ERROR_SUCCESS) { ret = IS_OPTION_FALSE(value[0]); } RegCloseKey(hkey); } return ret; } /* this function creates the listener window */ void initialize_systray(void) { WNDCLASSEX class; static const WCHAR classname[] = /* Shell_TrayWnd */ {'S','h','e','l','l','_','T','r','a','y','W','n','d',0}; static const WCHAR winname[] = /* Wine Systray Listener */ {'W','i','n','e',' ','S','y','s','t','r','a','y',' ','L','i','s','t','e','n','e','r',0}; WINE_TRACE("initiaizing\n"); hide_systray = is_systray_hidden(); list_init(&tray.icons); /* register the systray listener window class */ ZeroMemory(&class, sizeof(class)); class.cbSize = sizeof(class); class.lpfnWndProc = &listener_wndproc; class.hInstance = NULL; class.hIcon = LoadIcon(0, IDI_WINLOGO); class.hCursor = LoadCursor(0, IDC_ARROW); class.hbrBackground = (HBRUSH) COLOR_WINDOW; class.lpszClassName = (WCHAR *) &classname; if (!RegisterClassEx(&class)) { WINE_ERR("Could not register SysTray window class\n"); return; } /* now register the adaptor window class */ ZeroMemory(&class, sizeof(class)); class.cbSize = sizeof(class); class.lpfnWndProc = adaptor_wndproc; class.hInstance = NULL; class.hIcon = LoadIcon(0, IDI_WINLOGO); class.hCursor = LoadCursor(0, IDC_ARROW); class.hbrBackground = (HBRUSH) COLOR_WINDOW; class.lpszClassName = adaptor_classname; class.style = CS_SAVEBITS | CS_DBLCLKS; if (!RegisterClassEx(&class)) { WINE_ERR("Could not register adaptor class\n"); return; } tray.window = CreateWindow(classname, winname, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, 0, 0, 0, 0); if (!tray.window) { WINE_ERR("Could not create tray window\n"); return; } }