|  | /* | 
|  | * X11 system tray management | 
|  | * | 
|  | * Copyright (C) 2004 Mike Hearn, for CodeWeavers | 
|  | * Copyright (C) 2005 Robert Shearman | 
|  | * Copyright (C) 2008 Alexandre Julliard | 
|  | * | 
|  | * 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 <assert.h> | 
|  | #include <stdarg.h> | 
|  | #include <stdlib.h> | 
|  | #include <stdio.h> | 
|  | #ifdef HAVE_UNISTD_H | 
|  | # include <unistd.h> | 
|  | #endif | 
|  |  | 
|  | #include <X11/Xlib.h> | 
|  |  | 
|  | #define NONAMELESSUNION | 
|  | #include "windef.h" | 
|  | #include "winbase.h" | 
|  | #include "wingdi.h" | 
|  | #include "winuser.h" | 
|  | #include "commctrl.h" | 
|  | #include "shellapi.h" | 
|  |  | 
|  | #include "x11drv.h" | 
|  | #include "wine/list.h" | 
|  | #include "wine/debug.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(systray); | 
|  |  | 
|  | /* an individual systray icon */ | 
|  | struct tray_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           state;    /* state flags */ | 
|  | UINT           id;       /* the unique id given by the app */ | 
|  | UINT           callback_message; | 
|  | int            display;  /* display index, or -1 if hidden */ | 
|  | WCHAR          tiptext[128];    /* tooltip text */ | 
|  | WCHAR          info_text[256];  /* info balloon text */ | 
|  | WCHAR          info_title[64];  /* info balloon title */ | 
|  | UINT           info_flags;      /* flags for info balloon */ | 
|  | UINT           info_timeout;    /* timeout for info balloon */ | 
|  | HICON          info_icon;       /* info balloon icon */ | 
|  | }; | 
|  |  | 
|  | static struct list icon_list = LIST_INIT( icon_list ); | 
|  |  | 
|  | static const WCHAR icon_classname[] = {'_','_','w','i','n','e','x','1','1','_','t','r','a','y','_','i','c','o','n',0}; | 
|  | static const WCHAR tray_classname[] = {'_','_','w','i','n','e','x','1','1','_','s','t','a','n','d','a','l','o','n','e','_','t','r','a','y',0}; | 
|  |  | 
|  | static BOOL show_icon( struct tray_icon *icon ); | 
|  | static BOOL hide_icon( struct tray_icon *icon ); | 
|  | static BOOL delete_icon( struct tray_icon *icon ); | 
|  |  | 
|  | #define SYSTEM_TRAY_REQUEST_DOCK  0 | 
|  | #define SYSTEM_TRAY_BEGIN_MESSAGE   1 | 
|  | #define SYSTEM_TRAY_CANCEL_MESSAGE  2 | 
|  |  | 
|  | Atom systray_atom = 0; | 
|  |  | 
|  | #define MIN_DISPLAYED 8 | 
|  | #define ICON_BORDER 2 | 
|  |  | 
|  | #define VALID_WIN_TIMER      1 | 
|  | #define BALLOON_CREATE_TIMER 2 | 
|  | #define BALLOON_SHOW_TIMER   3 | 
|  |  | 
|  | #define VALID_WIN_TIMEOUT        2000 | 
|  | #define BALLOON_CREATE_TIMEOUT   2000 | 
|  | #define BALLOON_SHOW_MIN_TIMEOUT 10000 | 
|  | #define BALLOON_SHOW_MAX_TIMEOUT 30000 | 
|  |  | 
|  | static struct tray_icon *balloon_icon; | 
|  | static HWND balloon_window; | 
|  | static POINT balloon_pos; | 
|  |  | 
|  | /* stand-alone tray window */ | 
|  | static HWND standalone_tray; | 
|  | static int icon_cx, icon_cy; | 
|  | static unsigned int nb_displayed; | 
|  |  | 
|  | /* retrieves 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; | 
|  | } | 
|  |  | 
|  | static void init_common_controls(void) | 
|  | { | 
|  | static BOOL initialized = FALSE; | 
|  |  | 
|  | if (!initialized) | 
|  | { | 
|  | INITCOMMONCONTROLSEX init_tooltip; | 
|  |  | 
|  | init_tooltip.dwSize = sizeof(INITCOMMONCONTROLSEX); | 
|  | init_tooltip.dwICC = ICC_TAB_CLASSES; | 
|  |  | 
|  | InitCommonControlsEx(&init_tooltip); | 
|  | initialized = TRUE; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* create tooltip window for icon */ | 
|  | static void create_tooltip(struct tray_icon *icon) | 
|  | { | 
|  | init_common_controls(); | 
|  | icon->tooltip = CreateWindowExW( WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL, | 
|  | WS_POPUP | TTS_ALWAYSTIP, | 
|  | CW_USEDEFAULT, CW_USEDEFAULT, | 
|  | CW_USEDEFAULT, CW_USEDEFAULT, | 
|  | icon->window, NULL, NULL, NULL); | 
|  | if (icon->tooltip) | 
|  | { | 
|  | TTTOOLINFOW ti; | 
|  | ZeroMemory(&ti, sizeof(ti)); | 
|  | ti.cbSize = sizeof(TTTOOLINFOW); | 
|  | ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND; | 
|  | ti.hwnd = icon->window; | 
|  | ti.uId = (UINT_PTR)icon->window; | 
|  | ti.lpszText = icon->tiptext; | 
|  | SendMessageW(icon->tooltip, TTM_ADDTOOLW, 0, (LPARAM)&ti); | 
|  | } | 
|  | } | 
|  |  | 
|  | void update_systray_balloon_position(void) | 
|  | { | 
|  | RECT rect; | 
|  | POINT pos; | 
|  |  | 
|  | if (!balloon_icon) return; | 
|  | GetWindowRect( balloon_icon->window, &rect ); | 
|  | pos.x = (rect.left + rect.right) / 2; | 
|  | pos.y = (rect.top + rect.bottom) / 2; | 
|  | if (pos.x == balloon_pos.x && pos.y == balloon_pos.y) return;  /* nothing changed */ | 
|  | balloon_pos = pos; | 
|  | SendMessageW( balloon_window, TTM_TRACKPOSITION, 0, MAKELONG( pos.x, pos.y )); | 
|  | } | 
|  |  | 
|  | static void balloon_create_timer( struct tray_icon *icon ) | 
|  | { | 
|  | TTTOOLINFOW ti; | 
|  |  | 
|  | init_common_controls(); | 
|  | balloon_window = CreateWindowExW( WS_EX_TOPMOST, TOOLTIPS_CLASSW, NULL, | 
|  | WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE, | 
|  | CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, | 
|  | icon->window, NULL, NULL, NULL); | 
|  |  | 
|  | memset( &ti, 0, sizeof(ti) ); | 
|  | ti.cbSize = sizeof(TTTOOLINFOW); | 
|  | ti.hwnd = icon->window; | 
|  | ti.uId = (UINT_PTR)icon->window; | 
|  | ti.uFlags = TTF_TRACK | TTF_IDISHWND; | 
|  | ti.lpszText = icon->info_text; | 
|  | SendMessageW( balloon_window, TTM_ADDTOOLW, 0, (LPARAM)&ti ); | 
|  | if ((icon->info_flags & NIIF_ICONMASK) == NIIF_USER) | 
|  | SendMessageW( balloon_window, TTM_SETTITLEW, (WPARAM)icon->info_icon, (LPARAM)icon->info_title ); | 
|  | else | 
|  | SendMessageW( balloon_window, TTM_SETTITLEW, icon->info_flags, (LPARAM)icon->info_title ); | 
|  | balloon_icon = icon; | 
|  | balloon_pos.x = balloon_pos.y = MAXLONG; | 
|  | update_systray_balloon_position(); | 
|  | SendMessageW( balloon_window, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti ); | 
|  | KillTimer( icon->window, BALLOON_CREATE_TIMER ); | 
|  | SetTimer( icon->window, BALLOON_SHOW_TIMER, icon->info_timeout, NULL ); | 
|  | } | 
|  |  | 
|  | static BOOL show_balloon( struct tray_icon *icon ) | 
|  | { | 
|  | if (standalone_tray && !show_systray) return FALSE;  /* no systray window */ | 
|  | if (!icon->window) return FALSE;  /* not displayed */ | 
|  | if (!icon->info_text[0]) return FALSE;  /* no balloon */ | 
|  | balloon_icon = icon; | 
|  | SetTimer( icon->window, BALLOON_CREATE_TIMER, BALLOON_CREATE_TIMEOUT, NULL ); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static void hide_balloon(void) | 
|  | { | 
|  | if (!balloon_icon) return; | 
|  | if (balloon_window) | 
|  | { | 
|  | KillTimer( balloon_icon->window, BALLOON_SHOW_TIMER ); | 
|  | DestroyWindow( balloon_window ); | 
|  | balloon_window = 0; | 
|  | } | 
|  | else KillTimer( balloon_icon->window, BALLOON_CREATE_TIMER ); | 
|  | balloon_icon = NULL; | 
|  | } | 
|  |  | 
|  | static void show_next_balloon(void) | 
|  | { | 
|  | struct tray_icon *icon; | 
|  |  | 
|  | LIST_FOR_EACH_ENTRY( icon, &icon_list, struct tray_icon, entry ) | 
|  | if (show_balloon( icon )) break; | 
|  | } | 
|  |  | 
|  | static void update_balloon( struct tray_icon *icon ) | 
|  | { | 
|  | if (balloon_icon == icon) | 
|  | { | 
|  | hide_balloon(); | 
|  | show_balloon( icon ); | 
|  | } | 
|  | else if (!balloon_icon) | 
|  | { | 
|  | if (!show_balloon( icon )) return; | 
|  | } | 
|  | if (!balloon_icon) show_next_balloon(); | 
|  | } | 
|  |  | 
|  | static void balloon_timer(void) | 
|  | { | 
|  | if (balloon_icon) balloon_icon->info_text[0] = 0;  /* clear text now that balloon has been shown */ | 
|  | hide_balloon(); | 
|  | show_next_balloon(); | 
|  | } | 
|  |  | 
|  | /* synchronize tooltip text with tooltip window */ | 
|  | static void update_tooltip_text(struct tray_icon *icon) | 
|  | { | 
|  | TTTOOLINFOW ti; | 
|  |  | 
|  | ZeroMemory(&ti, sizeof(ti)); | 
|  | ti.cbSize = sizeof(TTTOOLINFOW); | 
|  | ti.uFlags = TTF_SUBCLASS | TTF_IDISHWND; | 
|  | ti.hwnd = icon->window; | 
|  | ti.uId = (UINT_PTR)icon->window; | 
|  | ti.lpszText = icon->tiptext; | 
|  |  | 
|  | SendMessageW(icon->tooltip, TTM_UPDATETIPTEXTW, 0, (LPARAM)&ti); | 
|  | } | 
|  |  | 
|  | /* get the size of the stand-alone tray window */ | 
|  | static SIZE get_window_size(void) | 
|  | { | 
|  | SIZE size; | 
|  | RECT rect; | 
|  |  | 
|  | rect.left = 0; | 
|  | rect.top = 0; | 
|  | rect.right = icon_cx * max( nb_displayed, MIN_DISPLAYED ); | 
|  | rect.bottom = icon_cy; | 
|  | AdjustWindowRect( &rect, WS_CAPTION, FALSE ); | 
|  | size.cx = rect.right - rect.left; | 
|  | size.cy = rect.bottom - rect.top; | 
|  | return size; | 
|  | } | 
|  |  | 
|  | /* get the position of an icon in the stand-alone tray */ | 
|  | static POINT get_icon_pos( struct tray_icon *icon ) | 
|  | { | 
|  | POINT pos; | 
|  |  | 
|  | pos.x = icon_cx * icon->display; | 
|  | pos.y = 0; | 
|  | return pos; | 
|  | } | 
|  |  | 
|  | /* window procedure for the standalone tray window */ | 
|  | static LRESULT WINAPI standalone_tray_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) | 
|  | { | 
|  | switch (msg) | 
|  | { | 
|  | case WM_MOVE: | 
|  | update_systray_balloon_position(); | 
|  | break; | 
|  | case WM_CLOSE: | 
|  | ShowWindow( hwnd, SW_HIDE ); | 
|  | hide_balloon(); | 
|  | show_systray = FALSE; | 
|  | return 0; | 
|  | case WM_DESTROY: | 
|  | standalone_tray = 0; | 
|  | break; | 
|  | } | 
|  | return DefWindowProcW( hwnd, msg, wparam, lparam ); | 
|  | } | 
|  |  | 
|  | /* add an icon to the standalone tray window */ | 
|  | static void add_to_standalone_tray( struct tray_icon *icon ) | 
|  | { | 
|  | SIZE size; | 
|  | POINT pos; | 
|  |  | 
|  | if (!standalone_tray) | 
|  | { | 
|  | static const WCHAR winname[] = {'W','i','n','e',' ','S','y','s','t','e','m',' ','T','r','a','y',0}; | 
|  |  | 
|  | size = get_window_size(); | 
|  | standalone_tray = CreateWindowExW( 0, tray_classname, winname, WS_CAPTION | WS_SYSMENU, | 
|  | CW_USEDEFAULT, CW_USEDEFAULT, size.cx, size.cy, 0, 0, 0, 0 ); | 
|  | if (!standalone_tray) return; | 
|  | } | 
|  |  | 
|  | icon->display = nb_displayed; | 
|  | pos = get_icon_pos( icon ); | 
|  | icon->window = CreateWindowW( icon_classname, NULL, WS_CHILD | WS_VISIBLE, | 
|  | pos.x, pos.y, icon_cx, icon_cy, standalone_tray, NULL, NULL, icon ); | 
|  | if (!icon->window) | 
|  | { | 
|  | icon->display = -1; | 
|  | return; | 
|  | } | 
|  | create_tooltip( icon ); | 
|  |  | 
|  | nb_displayed++; | 
|  | size = get_window_size(); | 
|  | SetWindowPos( standalone_tray, 0, 0, 0, size.cx, size.cy, SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOZORDER ); | 
|  | if (nb_displayed == 1 && show_systray) ShowWindow( standalone_tray, SW_SHOWNA ); | 
|  | TRACE( "added %u now %d icons\n", icon->id, nb_displayed ); | 
|  | } | 
|  |  | 
|  | /* remove an icon from the stand-alone tray */ | 
|  | static void remove_from_standalone_tray( struct tray_icon *icon ) | 
|  | { | 
|  | struct tray_icon *ptr; | 
|  | POINT pos; | 
|  |  | 
|  | if (icon->display == -1) return; | 
|  |  | 
|  | LIST_FOR_EACH_ENTRY( ptr, &icon_list, struct tray_icon, entry ) | 
|  | { | 
|  | if (ptr == icon) continue; | 
|  | if (ptr->display < icon->display) continue; | 
|  | ptr->display--; | 
|  | pos = get_icon_pos( ptr ); | 
|  | SetWindowPos( ptr->window, 0, pos.x, pos.y, 0, 0, SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER ); | 
|  | } | 
|  | icon->display = -1; | 
|  | if (!--nb_displayed) ShowWindow( standalone_tray, SW_HIDE ); | 
|  | TRACE( "removed %u now %d icons\n", icon->id, nb_displayed ); | 
|  | } | 
|  |  | 
|  | /* window procedure for the individual tray icon window */ | 
|  | static LRESULT WINAPI tray_icon_wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) | 
|  | { | 
|  | struct tray_icon *icon = NULL; | 
|  | BOOL ret; | 
|  |  | 
|  | WINE_TRACE("hwnd=%p, msg=0x%x\n", hwnd, msg); | 
|  |  | 
|  | /* set the icon data for the window from the data passed into CreateWindow */ | 
|  | if (msg == WM_NCCREATE) | 
|  | SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LPARAM)((const CREATESTRUCTW *)lparam)->lpCreateParams); | 
|  |  | 
|  | icon = (struct tray_icon *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); | 
|  |  | 
|  | switch (msg) | 
|  | { | 
|  | case WM_CREATE: | 
|  | SetTimer( hwnd, VALID_WIN_TIMER, VALID_WIN_TIMEOUT, NULL ); | 
|  | break; | 
|  |  | 
|  | case WM_PAINT: | 
|  | { | 
|  | PAINTSTRUCT ps; | 
|  | RECT rc; | 
|  | HDC hdc; | 
|  | int cx = GetSystemMetrics( SM_CXSMICON ); | 
|  | int cy = GetSystemMetrics( SM_CYSMICON ); | 
|  |  | 
|  | hdc = BeginPaint(hwnd, &ps); | 
|  | GetClientRect(hwnd, &rc); | 
|  | TRACE("painting rect %s\n", wine_dbgstr_rect(&rc)); | 
|  | DrawIconEx( hdc, (rc.left + rc.right - cx) / 2, (rc.top + rc.bottom - cy) / 2, | 
|  | icon->image, cx, cy, 0, 0, DI_DEFAULTSIZE|DI_NORMAL ); | 
|  | EndPaint(hwnd, &ps); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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 */ | 
|  | TRACE("relaying 0x%x\n", msg); | 
|  | ret = PostMessageW(icon->owner, icon->callback_message, icon->id, msg); | 
|  | if (!ret && (GetLastError() == ERROR_INVALID_WINDOW_HANDLE)) | 
|  | { | 
|  | WARN( "application window was destroyed, removing icon %u\n", icon->id ); | 
|  | delete_icon( icon ); | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | case WM_WINDOWPOSCHANGED: | 
|  | update_systray_balloon_position(); | 
|  | break; | 
|  |  | 
|  | case WM_TIMER: | 
|  | switch (wparam) | 
|  | { | 
|  | case VALID_WIN_TIMER: | 
|  | if (!IsWindow( icon->owner )) delete_icon( icon ); | 
|  | break; | 
|  | case BALLOON_CREATE_TIMER: | 
|  | balloon_create_timer( icon ); | 
|  | break; | 
|  | case BALLOON_SHOW_TIMER: | 
|  | balloon_timer(); | 
|  | break; | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | case WM_CLOSE: | 
|  | if (icon->display == -1) | 
|  | { | 
|  | TRACE( "icon %u no longer embedded\n", icon->id ); | 
|  | hide_icon( icon ); | 
|  | add_to_standalone_tray( icon ); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | return DefWindowProcW( hwnd, msg, wparam, lparam ); | 
|  | } | 
|  |  | 
|  | /* find the X11 window owner the system tray selection */ | 
|  | static Window get_systray_selection_owner( Display *display ) | 
|  | { | 
|  | Window ret; | 
|  |  | 
|  | wine_tsx11_lock(); | 
|  | ret = XGetSelectionOwner( display, systray_atom ); | 
|  | wine_tsx11_unlock(); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static BOOL init_systray(void) | 
|  | { | 
|  | static BOOL init_done; | 
|  | WNDCLASSEXW class; | 
|  | Display *display; | 
|  |  | 
|  | if (root_window != DefaultRootWindow( gdi_display )) return FALSE; | 
|  | if (init_done) return TRUE; | 
|  |  | 
|  | icon_cx = GetSystemMetrics( SM_CXSMICON ) + 2 * ICON_BORDER; | 
|  | icon_cy = GetSystemMetrics( SM_CYSMICON ) + 2 * ICON_BORDER; | 
|  |  | 
|  | memset( &class, 0, sizeof(class) ); | 
|  | class.cbSize        = sizeof(class); | 
|  | class.lpfnWndProc   = tray_icon_wndproc; | 
|  | class.hIcon         = LoadIconW(0, (LPCWSTR)IDI_WINLOGO); | 
|  | class.hCursor       = LoadCursorW( 0, (LPCWSTR)IDC_ARROW ); | 
|  | class.lpszClassName = icon_classname; | 
|  | class.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; | 
|  |  | 
|  | if (!RegisterClassExW( &class ) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) | 
|  | { | 
|  | ERR( "Could not register icon tray window class\n" ); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | class.lpfnWndProc   = standalone_tray_wndproc; | 
|  | class.hbrBackground = (HBRUSH)COLOR_WINDOW; | 
|  | class.lpszClassName = tray_classname; | 
|  | class.style         = CS_DBLCLKS; | 
|  |  | 
|  | if (!RegisterClassExW( &class ) && GetLastError() != ERROR_CLASS_ALREADY_EXISTS) | 
|  | { | 
|  | ERR( "Could not register standalone tray window class\n" ); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | display = thread_init_display(); | 
|  | wine_tsx11_lock(); | 
|  | if (DefaultScreen( display ) == 0) | 
|  | systray_atom = x11drv_atom(_NET_SYSTEM_TRAY_S0); | 
|  | else | 
|  | { | 
|  | char systray_buffer[29]; /* strlen(_NET_SYSTEM_TRAY_S4294967295)+1 */ | 
|  | sprintf( systray_buffer, "_NET_SYSTEM_TRAY_S%u", DefaultScreen( display ) ); | 
|  | systray_atom = XInternAtom( display, systray_buffer, False ); | 
|  | } | 
|  | XSelectInput( display, root_window, StructureNotifyMask ); | 
|  | wine_tsx11_unlock(); | 
|  |  | 
|  | init_done = TRUE; | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* dock the given icon with the NETWM system tray */ | 
|  | static void dock_systray_icon( Display *display, struct tray_icon *icon, Window systray_window ) | 
|  | { | 
|  | struct x11drv_win_data *data; | 
|  | XEvent ev; | 
|  | XSetWindowAttributes attr; | 
|  |  | 
|  | icon->window = CreateWindowW( icon_classname, NULL, WS_CLIPSIBLINGS | WS_POPUP, | 
|  | CW_USEDEFAULT, CW_USEDEFAULT, icon_cx, icon_cy, | 
|  | NULL, NULL, NULL, icon ); | 
|  | if (!icon->window) return; | 
|  |  | 
|  | if (!(data = X11DRV_get_win_data( icon->window )) && | 
|  | !(data = X11DRV_create_win_data( icon->window ))) return; | 
|  |  | 
|  | TRACE( "icon window %p/%lx managed %u\n", data->hwnd, data->whole_window, data->managed ); | 
|  |  | 
|  | make_window_embedded( display, data ); | 
|  | create_tooltip( icon ); | 
|  | ShowWindow( icon->window, SW_SHOWNA ); | 
|  |  | 
|  | /* send the docking request message */ | 
|  | ev.xclient.type = ClientMessage; | 
|  | ev.xclient.window = systray_window; | 
|  | ev.xclient.message_type = x11drv_atom( _NET_SYSTEM_TRAY_OPCODE ); | 
|  | ev.xclient.format = 32; | 
|  | ev.xclient.data.l[0] = CurrentTime; | 
|  | ev.xclient.data.l[1] = SYSTEM_TRAY_REQUEST_DOCK; | 
|  | ev.xclient.data.l[2] = data->whole_window; | 
|  | ev.xclient.data.l[3] = 0; | 
|  | ev.xclient.data.l[4] = 0; | 
|  | wine_tsx11_lock(); | 
|  | XSendEvent( display, systray_window, False, NoEventMask, &ev ); | 
|  | attr.background_pixmap = ParentRelative; | 
|  | attr.bit_gravity = ForgetGravity; | 
|  | XChangeWindowAttributes( display, data->whole_window, CWBackPixmap | CWBitGravity, &attr ); | 
|  | XChangeWindowAttributes( display, data->client_window, CWBackPixmap | CWBitGravity, &attr ); | 
|  | wine_tsx11_unlock(); | 
|  | } | 
|  |  | 
|  | /* dock systray windows again with the new owner */ | 
|  | void change_systray_owner( Display *display, Window systray_window ) | 
|  | { | 
|  | struct tray_icon *icon; | 
|  |  | 
|  | TRACE( "new owner %lx\n", systray_window ); | 
|  | LIST_FOR_EACH_ENTRY( icon, &icon_list, struct tray_icon, entry ) | 
|  | { | 
|  | if (icon->display == -1) continue; | 
|  | hide_icon( icon ); | 
|  | dock_systray_icon( display, icon, systray_window ); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* hide a tray icon */ | 
|  | static BOOL hide_icon( struct tray_icon *icon ) | 
|  | { | 
|  | struct x11drv_win_data *data; | 
|  |  | 
|  | TRACE( "id=0x%x, hwnd=%p\n", icon->id, icon->owner ); | 
|  |  | 
|  | if (!icon->window) return TRUE;  /* already hidden */ | 
|  |  | 
|  | /* make sure we don't try to unmap it, it confuses some systray docks */ | 
|  | if ((data = X11DRV_get_win_data( icon->window )) && data->embedded) data->mapped = FALSE; | 
|  |  | 
|  | DestroyWindow(icon->window); | 
|  | DestroyWindow(icon->tooltip); | 
|  | icon->window = 0; | 
|  | icon->tooltip = 0; | 
|  | remove_from_standalone_tray( icon ); | 
|  | update_balloon( icon ); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* make the icon visible */ | 
|  | static BOOL show_icon( struct tray_icon *icon ) | 
|  | { | 
|  | Window systray_window; | 
|  | Display *display = thread_init_display(); | 
|  |  | 
|  | TRACE( "id=0x%x, hwnd=%p\n", icon->id, icon->owner ); | 
|  |  | 
|  | if (icon->window) return TRUE;  /* already shown */ | 
|  |  | 
|  | if ((systray_window = get_systray_selection_owner( display ))) | 
|  | dock_systray_icon( display, icon, systray_window ); | 
|  | else | 
|  | add_to_standalone_tray( icon ); | 
|  |  | 
|  | update_balloon( icon ); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* Modifies an existing icon record */ | 
|  | static BOOL modify_icon( struct tray_icon *icon, NOTIFYICONDATAW *nid ) | 
|  | { | 
|  | TRACE( "id=0x%x hwnd=%p flags=%x\n", nid->uID, nid->hWnd, nid->uFlags ); | 
|  |  | 
|  | if (nid->uFlags & NIF_STATE) | 
|  | { | 
|  | icon->state = (icon->state & ~nid->dwStateMask) | (nid->dwState & nid->dwStateMask); | 
|  | } | 
|  |  | 
|  | if (nid->uFlags & NIF_ICON) | 
|  | { | 
|  | if (icon->image) DestroyIcon(icon->image); | 
|  | icon->image = CopyIcon(nid->hIcon); | 
|  | if (icon->window) | 
|  | { | 
|  | if (icon->display != -1) InvalidateRect( icon->window, NULL, TRUE ); | 
|  | else | 
|  | { | 
|  | struct x11drv_win_data *data = X11DRV_get_win_data( icon->window ); | 
|  | if (data) XClearArea( gdi_display, data->client_window, 0, 0, 0, 0, True ); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | if (nid->uFlags & NIF_MESSAGE) | 
|  | { | 
|  | icon->callback_message = nid->uCallbackMessage; | 
|  | } | 
|  | if (nid->uFlags & NIF_TIP) | 
|  | { | 
|  | lstrcpynW(icon->tiptext, nid->szTip, sizeof(icon->tiptext)/sizeof(WCHAR)); | 
|  | if (icon->tooltip) update_tooltip_text(icon); | 
|  | } | 
|  | if (nid->uFlags & NIF_INFO && nid->cbSize >= NOTIFYICONDATAA_V2_SIZE) | 
|  | { | 
|  | lstrcpynW( icon->info_text, nid->szInfo, sizeof(icon->info_text)/sizeof(WCHAR) ); | 
|  | lstrcpynW( icon->info_title, nid->szInfoTitle, sizeof(icon->info_title)/sizeof(WCHAR) ); | 
|  | icon->info_flags = nid->dwInfoFlags; | 
|  | icon->info_timeout = max(min(nid->u.uTimeout, BALLOON_SHOW_MAX_TIMEOUT), BALLOON_SHOW_MIN_TIMEOUT); | 
|  | icon->info_icon = nid->hBalloonIcon; | 
|  | update_balloon( icon ); | 
|  | } | 
|  | if (icon->state & NIS_HIDDEN) hide_icon( icon ); | 
|  | else show_icon( icon ); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | /* Adds a new icon record to the list */ | 
|  | static BOOL add_icon(NOTIFYICONDATAW *nid) | 
|  | { | 
|  | struct tray_icon  *icon; | 
|  |  | 
|  | 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 FALSE; | 
|  | } | 
|  |  | 
|  | if (!(icon = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*icon)))) | 
|  | { | 
|  | WINE_ERR("out of memory\n"); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | ZeroMemory(icon, sizeof(struct tray_icon)); | 
|  | icon->id     = nid->uID; | 
|  | icon->owner  = nid->hWnd; | 
|  | icon->display = -1; | 
|  |  | 
|  | list_add_tail(&icon_list, &icon->entry); | 
|  |  | 
|  | return modify_icon( icon, nid ); | 
|  | } | 
|  |  | 
|  | /* delete tray icon window and icon structure */ | 
|  | static BOOL delete_icon( struct tray_icon *icon ) | 
|  | { | 
|  | hide_icon( icon ); | 
|  | list_remove( &icon->entry ); | 
|  | DestroyIcon( icon->image ); | 
|  | HeapFree( GetProcessHeap(), 0, icon ); | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  |  | 
|  | /*********************************************************************** | 
|  | *              wine_notify_icon   (X11DRV.@) | 
|  | * | 
|  | * 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: | 
|  | if (!init_systray()) return -1;  /* fall back to default handling */ | 
|  | 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; | 
|  | default: | 
|  | FIXME( "unhandled tray message: %u\n", msg ); | 
|  | break; | 
|  | } | 
|  | return ret; | 
|  | } |