I'm going to keep sending this flipping patch until it gets in, or I find out what the next problem is you know. There's no escape! :) ChangeLog Implement XEMBED system tray support Index: dlls/shell32/systray.c =================================================================== RCS file: /home/wine/wine/dlls/shell32/systray.c,v retrieving revision 1.21 diff -u -r1.21 systray.c --- dlls/shell32/systray.c 20 Aug 2003 18:22:31 -0000 1.21 +++ dlls/shell32/systray.c 28 Aug 2003 13:05:56 -0000 @@ -1,11 +1,11 @@ /* - * Systray + * System tray handling code (client side) * - * Copyright 1999 Kai Morich <kai.morich@bigfoot.de> + * Copyright 1999 Kai Morich <kai.morich@bigfoot.de> + * Copyright 2003 Mike Hearn <mike@theoretic.com> * - * Manage the systray window. That it actually appears in the docking - * area of KDE or GNOME is delegated to windows/x11drv/wnd.c, - * X11DRV_WND_DockWindow. + * This code creates a window with the WS_EX_TRAYWINDOW style. The actual + * environment integration code is handled inside the X11 driver. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -38,21 +38,21 @@ #include "commctrl.h" #include "wine/debug.h" -WINE_DEFAULT_DEBUG_CHANNEL(shell); +WINE_DEFAULT_DEBUG_CHANNEL(systray); typedef struct SystrayItem { HWND hWnd; HWND hWndToolTip; NOTIFYICONDATAA notifyIcon; + CRITICAL_SECTION lock; struct SystrayItem *nextTrayItem; } SystrayItem; -static SystrayItem *systray=NULL; -static int firstSystray=TRUE; /* defer creation of window class until first systray item is created */ +static SystrayItem *systray = NULL; +static int firstSystray = TRUE; /* defer creation of window class until first systray item is created */ static BOOL SYSTRAY_Delete(PNOTIFYICONDATAA pnid); - #define ICON_SIZE GetSystemMetrics(SM_CXSMICON) /* space around icon (forces icon to center of KDE systray area) */ #define ICON_BORDER 4 @@ -70,21 +70,26 @@ { HDC hdc; PAINTSTRUCT ps; - + TRACE("hwnd=%p, msg=0x%x\n", hWnd, message); switch (message) { case WM_PAINT: { RECT rc; SystrayItem *ptrayItem = systray; - + int top; + EnterCriticalSection(&ptrayItem->lock); + while (ptrayItem) { if (ptrayItem->hWnd==hWnd) { if (ptrayItem->notifyIcon.hIcon) { hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rc); - if (!DrawIconEx(hdc, rc.left+ICON_BORDER, rc.top+ICON_BORDER, ptrayItem->notifyIcon.hIcon, + /* calculate top so we can deal with arbitrary sized trays */ + top = ((rc.bottom-rc.top)/2) - ((ICON_SIZE)/2); + if (!DrawIconEx(hdc, (ICON_BORDER/2), top, ptrayItem->notifyIcon.hIcon, ICON_SIZE, ICON_SIZE, 0, 0, DI_DEFAULTSIZE|DI_NORMAL)) { ERR("Paint(SystrayWindow %p) failed -> removing SystrayItem %p\n", hWnd, ptrayItem); + LeaveCriticalSection(&ptrayItem->lock); SYSTRAY_Delete(&ptrayItem->notifyIcon); } } @@ -93,10 +98,10 @@ ptrayItem = ptrayItem->nextTrayItem; } EndPaint(hWnd, &ps); + LeaveCriticalSection(&ptrayItem->lock); } break; - case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: @@ -106,7 +111,7 @@ { MSG msg; SystrayItem *ptrayItem = systray; - + /* relay the event to the tooltip */ while ( ptrayItem ) { if (ptrayItem->hWnd == hWnd) { msg.hwnd=hWnd; @@ -122,14 +127,14 @@ ptrayItem = ptrayItem->nextTrayItem; } } - /* fall through */ + /* fall through, so the message is sent to the callback as well */ case WM_LBUTTONDBLCLK: case WM_RBUTTONDBLCLK: case WM_MBUTTONDBLCLK: { SystrayItem *ptrayItem = systray; - + /* iterate over the currently active tray items */ while (ptrayItem) { if (ptrayItem->hWnd == hWnd) { if (ptrayItem->notifyIcon.hWnd && ptrayItem->notifyIcon.uCallbackMessage) { @@ -146,6 +151,14 @@ } break; + case WM_NOTIFYFORMAT: + { + TRACE("Received WM_NOTIFYFORMAT, showing the tray window\n"); + ShowWindow(hWnd, SW_SHOW); + return (DefWindowProcA(hWnd, message, wParam, lParam)); + } + + default: return (DefWindowProcA(hWnd, message, wParam, lParam)); } @@ -177,30 +190,21 @@ } -BOOL SYSTRAY_ItemInit(SystrayItem *ptrayItem) -{ +DWORD WINAPI SYSTRAY_ThreadProc(LPVOID p1) { + SystrayItem *ptrayItem = (SystrayItem *)p1; + MSG msg; RECT rect; - - /* Register the class if this is our first tray item. */ - if ( firstSystray ) { - firstSystray = FALSE; - if ( !SYSTRAY_RegisterClass() ) { - ERR( "RegisterClass(WineSystray) failed\n" ); - return FALSE; - } - } - + /* Initialize the window size. */ rect.left = 0; rect.top = 0; rect.right = ICON_SIZE+2*ICON_BORDER; rect.bottom = ICON_SIZE+2*ICON_BORDER; - ZeroMemory( ptrayItem, sizeof(SystrayItem) ); /* Create tray window for icon. */ ptrayItem->hWnd = CreateWindowExA( WS_EX_TRAYWINDOW, "WineSystray", "Wine-Systray", - WS_VISIBLE, + 0, CW_USEDEFAULT, CW_USEDEFAULT, rect.right-rect.left, rect.bottom-rect.top, 0, 0, 0, 0 ); @@ -218,31 +222,72 @@ ERR( "CreateWindow(TOOLTIP) failed\n" ); return FALSE; } + + /* Enter the message loop */ + while (GetMessageA (&msg, 0, 0, 0) > 0) { + TranslateMessage (&msg); + DispatchMessageA (&msg); + } + + TRACE("Shutting down system tray thread\n"); + if(ptrayItem->notifyIcon.hIcon) + DestroyIcon(ptrayItem->notifyIcon.hIcon); + if(ptrayItem->hWndToolTip) + DestroyWindow(ptrayItem->hWndToolTip); + + return 0; +} + +BOOL SYSTRAY_ItemInit(SystrayItem *ptrayItem) +{ + DWORD threadID; + + /* Register the class if this is our first tray item. */ + if ( firstSystray ) { + firstSystray = FALSE; + if ( !SYSTRAY_RegisterClass() ) { + ERR( "RegisterClass(WineSystray) failed\n" ); + return FALSE; + } + } + + ZeroMemory( ptrayItem, sizeof(SystrayItem) ); + + /* We need to run the system tray window in a separate thread, as otherwise if the originating thread + stops processing messages, the tray window will hang. If another part of the application then does + for instance a FindWindow call, this can deadlock the application. */ + InitializeCriticalSection(&ptrayItem->lock); + if (!CreateThread(NULL, 0, SYSTRAY_ThreadProc, (LPVOID) ptrayItem, 0, &threadID)) { + ERR("Could not create system tray item thread\n"); + return FALSE; + } return TRUE; } static void SYSTRAY_ItemTerm(SystrayItem *ptrayItem) { - if(ptrayItem->notifyIcon.hIcon) - DestroyIcon(ptrayItem->notifyIcon.hIcon); - if(ptrayItem->hWndToolTip) - DestroyWindow(ptrayItem->hWndToolTip); - if(ptrayItem->hWnd) - DestroyWindow(ptrayItem->hWnd); + /* MSDN says we shouldn't do this, but I can't see another way to make GetMessage() return zero */ + PostMessageA(ptrayItem->hWnd, WM_QUIT, 0, 0); + DeleteCriticalSection(&ptrayItem->lock); return; } void SYSTRAY_ItemSetMessage(SystrayItem *ptrayItem, UINT uCallbackMessage) { + EnterCriticalSection(&ptrayItem->lock); ptrayItem->notifyIcon.uCallbackMessage = uCallbackMessage; + LeaveCriticalSection(&ptrayItem->lock); } void SYSTRAY_ItemSetIcon(SystrayItem *ptrayItem, HICON hIcon) { + EnterCriticalSection(&ptrayItem->lock); ptrayItem->notifyIcon.hIcon = CopyIcon(hIcon); + LeaveCriticalSection(&ptrayItem->lock); + InvalidateRect(ptrayItem->hWnd, NULL, TRUE); } @@ -251,9 +296,11 @@ { TTTOOLINFOA ti; - strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip)); + EnterCriticalSection(&ptrayItem->lock); + strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip)); ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0; - + LeaveCriticalSection(&ptrayItem->lock); + ti.cbSize = sizeof(TTTOOLINFOA); ti.uFlags = 0; ti.hwnd = ptrayItem->hWnd; Index: dlls/x11drv/window.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/window.c,v retrieving revision 1.56 diff -u -r1.56 window.c --- dlls/x11drv/window.c 8 Aug 2003 21:07:23 -0000 1.56 +++ dlls/x11drv/window.c 28 Aug 2003 13:06:02 -0000 @@ -4,6 +4,7 @@ * Copyright 1993, 1994, 1995, 1996, 2001 Alexandre Julliard * Copyright 1993 David Metcalfe * Copyright 1995, 1996 Alex Korobka + * Copyright 2003 Mike Hearn * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,6 +27,7 @@ #ifdef HAVE_UNISTD_H # include <unistd.h> #endif +#include <stdio.h> #include "ts_xlib.h" #include <X11/Xresource.h> @@ -44,6 +46,7 @@ #include "mwm.h" WINE_DEFAULT_DEBUG_CHANNEL(x11drv); +WINE_DECLARE_DEBUG_CHANNEL(systray); extern Pixmap X11DRV_BITMAP_Pixmap( HBITMAP ); @@ -57,6 +60,7 @@ Atom wmProtocols = None; Atom wmDeleteWindow = None; Atom wmTakeFocus = None; +Atom wmManager = None; Atom dndProtocol = None; Atom dndSelection = None; Atom wmChangeState = None; @@ -64,12 +68,23 @@ Atom kwmDockWindow = None; Atom netwmPid = None; Atom netwmPing = None; +Atom netSysTraySelection = None; +Atom netSysTrayOpcode = None; +Atom xembedInfo = None; +Atom xembed = None; Atom _kde_net_wm_system_tray_window_for = None; /* KDE 2 Final */ +Window systrayWindow; + static LPCSTR whole_window_atom; static LPCSTR client_window_atom; static LPCSTR icon_window_atom; +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + + /*********************************************************************** * is_window_managed * @@ -317,11 +332,59 @@ size_hints->min_height = size_hints->max_height; size_hints->flags |= PMinSize | PMaxSize; } + if (win->dwExStyle & WS_EX_TRAYWINDOW) { + /* force the window to be the correct width */ + size_hints->min_width = GetSystemMetrics(SM_CXSMICON) + 5; /* give some padding to make icons not bunched up */ + } XSetWMNormalHints( display, data->whole_window, size_hints ); XFree( size_hints ); } } +/*********************************************************************** + * X11DRV_systray_dock_window + * + * Docks the given X window with the NETWM system tray. + */ +BOOL CALLBACK X11DRV_systray_dock_window( HWND hwnd, Display *display ) { + WND* win = WIN_GetPtr((HWND)hwnd); + struct x11drv_win_data *data = win->pDriverData; + XEvent ev; + unsigned long info[2]; + LONG exstyle; + + /* is the window a tray window? */ + if (IsWindowUnicode(hwnd)) + exstyle = GetWindowLongW(hwnd, GWL_EXSTYLE); + else + exstyle = GetWindowLongA(hwnd, GWL_EXSTYLE); + if ( !(exstyle & WS_EX_TRAYWINDOW) ) return TRUE; + + TRACE_(systray)("Docking tray icon 0x%x\n", (int)hwnd); + wine_tsx11_lock(); + + /* set XEMBED protocol data on the window */ + info[0] = 0; /* protocol version */ + info[1] = 0; /* mapped = true */ + XChangeProperty(display, data->whole_window, xembedInfo, xembedInfo, 32, PropModeReplace, (unsigned char*)info, 2); + + /* send the docking request message */ + memset(&ev, 0, sizeof(ev)); + ev.xclient.type = ClientMessage; + ev.xclient.window = systrayWindow; + ev.xclient.message_type = netSysTrayOpcode; + 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; + XSendEvent(display, systrayWindow, False, NoEventMask, &ev); + XSync(display, False); + wine_tsx11_unlock(); + + WIN_ReleasePtr(win); + return TRUE; +} + /*********************************************************************** * X11DRV_set_wm_hints @@ -367,16 +430,19 @@ /* size hints */ set_size_hints( display, win ); - /* systray properties (KDE only for now) */ + /* systray properties */ if (win->dwExStyle & WS_EX_TRAYWINDOW) { int val = 1; - if (kwmDockWindow != None) + + if (systrayWindow == None) { + if (kwmDockWindow != None) XChangeProperty( display, data->whole_window, kwmDockWindow, kwmDockWindow, 32, PropModeReplace, (char*)&val, 1 ); - if (_kde_net_wm_system_tray_window_for != None) + if (_kde_net_wm_system_tray_window_for != None) XChangeProperty( display, data->whole_window, _kde_net_wm_system_tray_window_for, XA_WINDOW, 32, PropModeReplace, (char*)&data->whole_window, 1 ); + } } /* set the WM_CLIENT_MACHINE and WM_LOCALE_NAME properties */ @@ -405,7 +471,7 @@ if (win->dwStyle & WS_SYSMENU) mwm_hints.decorations |= MWM_DECOR_MENU; if (win->dwStyle & WS_MINIMIZEBOX) mwm_hints.decorations |= MWM_DECOR_MINIMIZE; if (win->dwStyle & WS_MAXIMIZEBOX) mwm_hints.decorations |= MWM_DECOR_MAXIMIZE; - + XChangeProperty( display, data->whole_window, mwmHints, mwmHints, 32, PropModeReplace, (char*)&mwm_hints, sizeof(mwm_hints)/sizeof(long) ); } @@ -544,6 +610,7 @@ /* should use stack_mode Below but most window managers don't get it right */ /* so move it above the next one in Z order */ HWND next = GetWindow( win->hwndSelf, GW_HWNDNEXT ); + while (next && !(GetWindowLongW( next, GWL_STYLE ) & WS_VISIBLE)) next = GetWindow( next, GW_HWNDNEXT ); if (next) @@ -653,7 +720,8 @@ static void create_desktop( Display *display, WND *wndPtr ) { X11DRV_WND_DATA *data = wndPtr->pDriverData; - + char *buffer; + wine_tsx11_lock(); winContext = XUniqueContext(); wmProtocols = XInternAtom( display, "WM_PROTOCOLS", False ); @@ -667,6 +735,18 @@ _kde_net_wm_system_tray_window_for = XInternAtom( display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", False ); netwmPid = XInternAtom( display, "_NET_WM_PID", False ); netwmPing = XInternAtom( display, "_NET_WM_PING", False ); + xembedInfo = XInternAtom( display, "_XEMBED_INFO", False ); + xembed = XInternAtom( display, "_XEMBED", False ); + systrayWindow = None; + + /* get system tray atoms */ + buffer = malloc(sizeof(char)*20); + sprintf(buffer, "_NET_SYSTEM_TRAY_S%d", DefaultScreen(display)); + netSysTraySelection = XInternAtom(display, buffer, False); + free(buffer); + netSysTrayOpcode = XInternAtom(display, "_NET_SYSTEM_TRAY_OPCODE", False); + wmManager = XInternAtom(display, "MANAGER", False); + wine_tsx11_unlock(); whole_window_atom = MAKEINTATOMA( GlobalAddAtomA( "__wine_x11_whole_window" )); @@ -681,6 +761,8 @@ SetPropA( wndPtr->hwndSelf, "__wine_x11_visual_id", (HANDLE)XVisualIDFromVisual(visual) ); if (root_window != DefaultRootWindow(display)) X11DRV_create_desktop_thread(); + /* notify us of manager events, so we can monitor for system tray managers */ + if (root_window == DefaultRootWindow(display)) XSelectInput(display, root_window, StructureNotifyMask); } @@ -951,7 +1033,7 @@ } /* Send the WM_GETMINMAXINFO message and fix the size if needed */ - if ((cs->style & WS_THICKFRAME) || !(cs->style & (WS_POPUP | WS_CHILD))) + if (((cs->style & WS_THICKFRAME) || !(cs->style & (WS_POPUP | WS_CHILD))) && !(cs->dwExStyle & WS_EX_TRAYWINDOW)) { POINT maxSize, maxPos, minTrack, maxTrack; @@ -1046,7 +1128,7 @@ } /* Show the window, maximizing or minimizing if needed */ - + if (wndPtr->dwStyle & (WS_MINIMIZE | WS_MAXIMIZE)) { extern UINT WINPOS_MinMaximize( HWND hwnd, UINT cmd, LPRECT rect ); /*FIXME*/ @@ -1066,6 +1148,13 @@ * we do a proper ShowWindow later on */ if (wndPtr->dwStyle & WS_VISIBLE) cs->style |= WS_VISIBLE; + /* if it's a tray window, dock it */ + if (wndPtr->dwExStyle & WS_EX_TRAYWINDOW) { + /* get the tray window if present */ + systrayWindow = XGetSelectionOwner(display, netSysTraySelection); + if (systrayWindow != None) + X11DRV_systray_dock_window(hwnd, display); + } WIN_ReleaseWndPtr( wndPtr ); return TRUE; @@ -1233,8 +1322,9 @@ /* we must not use CurrentTime (ICCCM), so try to use last message time instead */ /* FIXME: this is not entirely correct */ + /* NOTE - not using CurrentTime here causes focus problems with some fullscreen apps */ XSetInputFocus( display, win, RevertToParent, - /*CurrentTime*/ GetMessageTime() + X11DRV_server_startticks ); + CurrentTime /* GetMessageTime() + X11DRV_server_startticks */ ); if (X11DRV_PALETTE_PaletteFlags & X11DRV_PALETTE_PRIVATE) XInstallColormap( display, X11DRV_PALETTE_PaletteXColormap ); }