[ this version of the patch was dropped, so bumping it to the top of the queue again ] Last time I submitted this, it was dropped because of the non-thread safe global array. Doing some more work on this last night, I realised it was pointless anyway, as the tray applet will destroy any windows when it's deleted (ie if you remove it then readd it, we'd have to reconstruct any tray windows). I talked to andersca - he said that the EggTrayIcon code doesn't deal with this situation either, and that the upcoming XFIXES extension would let us deal with that scenario more intelligently. Therefore, I removed the relevant code, as it was unlikely to work properly without a lot more work. ChangeLog: Implement support for XEMBED system tray areas
Index: dlls/x11drv/event.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/event.c,v retrieving revision 1.22 diff -u -b -w -r1.22 event.c --- dlls/x11drv/event.c 8 Jul 2003 21:02:51 -0000 1.22 +++ dlls/x11drv/event.c 4 Aug 2003 10:54:01 -0000 @@ -3,6 +3,7 @@ * * Copyright 1993 Alexandre Julliard * 1999 Noel Borthwick + * 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 @@ -34,6 +35,7 @@ #include <assert.h> #include <string.h> +#include <stdio.h> #include "wine/winuser16.h" #include "shlobj.h" /* DROPFILES */ @@ -47,6 +49,7 @@ #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(event); +WINE_DECLARE_DEBUG_CHANNEL(systray); WINE_DECLARE_DEBUG_CHANNEL(clipboard); /* X context to associate a hwnd to an X window */ @@ -54,9 +57,14 @@ extern Atom wmProtocols; extern Atom wmDeleteWindow; +extern Atom wmManager; extern Atom dndProtocol; extern Atom dndSelection; extern Atom netwmPing; +extern Atom netSysTraySelection; +extern Atom xembed; + +extern Window systrayWindow; #define DndNotDnd -1 /* OffiX drag&drop */ #define DndUnknown 0 @@ -106,6 +114,7 @@ extern void X11DRV_UnmapNotify( HWND hwnd, XUnmapEvent *event ); extern void X11DRV_ConfigureNotify( HWND hwnd, XConfigureEvent *event ); extern void X11DRV_MappingNotify( XMappingEvent *event ); +extern void X11DRV_systray_dock_all( Display* display ); #ifdef HAVE_LIBXXF86DGA2 static int DGAMotionEventType; @@ -275,7 +284,10 @@ wine_tsx11_unlock(); if (!hWnd && event->xany.window == root_window) hWnd = GetDesktopWindow(); - if (!hWnd && event->type != PropertyNotify && event->type != MappingNotify) + if ( !hWnd && event->xany.window != root_window + && event->type != PropertyNotify + && event->type != MappingNotify + && event->type != ClientMessage) WARN( "Got event %s for unknown Window %08lx\n", event_names[event->type], event->xany.window ); else @@ -338,7 +350,6 @@ break; case ClientMessage: - if (!hWnd) return; EVENT_ClientMessage( hWnd, (XClientMessageEvent *) event ); break; @@ -1202,8 +1213,49 @@ */ static void EVENT_ClientMessage( HWND hWnd, XClientMessageEvent *event ) { + TRACE("called\n"); if (event->message_type != None && event->format == 32) { - if (event->message_type == wmProtocols) + if (event->message_type == wmManager) { + if (event->data.l[1] == netSysTraySelection) { + TRACE_(systray)("New NETWM systray manager detected, id=%ld\n", event->data.l[2]); + /* NOTE: It turns out that the ability to detect when a new tray applet joins the + * desktop is not as helpful as you might think. In order to do something useful with it, + * we would need to be able to "store" icons unmapped as children of the root window while + * no tray applet is available. + * + * The basic problem is that tray icons are always destroyed when the applet is removed. + * This is apparently an issue with X itself, which the upcoming XFIXES extension should hopefully + * address. The EggTrayIcon code which will be soon moving into GTK+ doesn't attempt to handle this + * situation, so for now neither do we. + * + * This is theoretically fixable in Wine with enough work, we just have to modify the code in + * shell32/systray.c to save the image and recreate the window on demand. Exactly how the + * communication between the x11drv and shell32 takes place is left as an excercise for the + * reader. + * -mike (3rd August 2003) + */ + } + } else if (event->message_type == xembed) { + char* opcode; + switch (event->data.l[1]) { + case 0: opcode = "XEMBED_EMBEDDED_NOTIFY"; break; + case 1: opcode = "XEMBED_WINDOW_ACTIVATE"; break; + case 2: opcode = "XEMBED_WINDOW_DEACTIVATE"; break; + case 3: opcode = "XEMBED_REQUEST_FOCUS"; break; + case 4: opcode = "XEMBED_FOCUS_IN"; break; + case 5: opcode = "XEMBED_FOCUS_OUT"; break; + case 6: opcode = "XEMBED_FOCUS_NEXT"; break; + case 7: opcode = "XEMEBD_FOCUS_PREV"; break; + case 10: opcode = "XEMBED_MODALITY_ON"; break; + case 11: opcode = "XEMBED_MODALITY_OFF"; break; + case 12: opcode = "XEMBED_REGISTER_ACCELERATOR"; break; + case 13: opcode = "XEMBED_UNREGISTER_ACCELERATOR"; break; + case 14: opcode = "XEMBED_ACTIVATE_ACCELERATOR"; break; + default: opcode = "[Unknown opcode]"; break; + } + TRACE_(systray)("XEmbed message, opcode is %s : %ld\n", opcode, event->data.l[1]); + /* we currently don't handle these messages */ + } else if (event->message_type == wmProtocols) handle_wm_protocols_message( hWnd, event ); else if (event->message_type == dndProtocol) { Index: dlls/x11drv/window.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/window.c,v retrieving revision 1.55 diff -u -b -w -r1.55 window.c --- dlls/x11drv/window.c 9 Jul 2003 04:22:57 -0000 1.55 +++ dlls/x11drv/window.c 4 Aug 2003 10:54:07 -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 @@ -24,6 +25,7 @@ #include <stdlib.h> #include <unistd.h> +#include <stdio.h> #include "ts_xlib.h" #include <X11/Xresource.h> @@ -42,6 +44,7 @@ #include "mwm.h" WINE_DEFAULT_DEBUG_CHANNEL(x11drv); +WINE_DECLARE_DEBUG_CHANNEL(systray); extern Pixmap X11DRV_BITMAP_Pixmap( HBITMAP ); @@ -55,6 +58,7 @@ Atom wmProtocols = None; Atom wmDeleteWindow = None; Atom wmTakeFocus = None; +Atom wmManager = None; Atom dndProtocol = None; Atom dndSelection = None; Atom wmChangeState = None; @@ -62,12 +66,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 * @@ -315,11 +330,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 @@ -365,10 +428,12 @@ /* 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 (systrayWindow == None) { if (kwmDockWindow != None) XChangeProperty( display, data->whole_window, kwmDockWindow, kwmDockWindow, 32, PropModeReplace, (char*)&val, 1 ); @@ -376,6 +441,7 @@ 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 */ XSetWMProperties(display, data->whole_window, NULL, NULL, NULL, 0, NULL, NULL, NULL); @@ -534,6 +600,7 @@ prev = GetWindow( prev, GW_HWNDPREV ); if (!prev) /* top child */ { + TRACE("Top Child\n"); changes.stack_mode = Above; mask |= CWStackMode; } @@ -542,6 +609,9 @@ /* 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 ); + + TRACE("Getting next window\n"); + while (next && !(GetWindowLongW( next, GWL_STYLE ) & WS_VISIBLE)) next = GetWindow( next, GW_HWNDNEXT ); if (next) @@ -557,6 +627,7 @@ HWND next = GetWindow( win->hwndSelf, GW_HWNDNEXT ); if (!next) /* bottom child */ { + TRACE("bottom child\n"); changes.stack_mode = Below; mask |= CWStackMode; } @@ -651,6 +722,7 @@ static void create_desktop( Display *display, WND *wndPtr ) { X11DRV_WND_DATA *data = wndPtr->pDriverData; + char* buffer; wine_tsx11_lock(); winContext = XUniqueContext(); @@ -665,6 +737,19 @@ _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)); + TRACE_(systray)("%s\n", buffer); + 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" )); @@ -679,6 +764,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); } @@ -772,6 +859,7 @@ attr.backing_store = NotUseful/*WhenMapped*/; wine_tsx11_lock(); + data->client_window = XCreateWindow( display, data->whole_window, 0, 0, max( rect.right - rect.left, 1 ), max( rect.bottom - rect.top, 1 ), @@ -949,7 +1037,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; @@ -1044,7 +1132,6 @@ } /* 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*/ @@ -1064,6 +1151,14 @@ * 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) + undockedTrayIcons[undockedTrayIconsCount++] = hwnd; + else X11DRV_systray_dock_window(hwnd, display); + } WIN_ReleaseWndPtr( wndPtr ); return TRUE; Index: dlls/shell32/systray.c =================================================================== RCS file: /home/wine/wine/dlls/shell32/systray.c,v retrieving revision 1.20 diff -u -b -w -r1.20 systray.c --- dlls/shell32/systray.c 24 Nov 2002 22:16:29 -0000 1.20 +++ dlls/shell32/systray.c 4 Aug 2003 10:54:09 -0000 @@ -1,11 +1,11 @@ /* - * Systray + * System tray handling code (client side) * * 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 @@ -37,12 +37,13 @@ #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; @@ -51,7 +52,6 @@ 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 @@ -69,21 +69,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); } } @@ -92,10 +97,10 @@ ptrayItem = ptrayItem->nextTrayItem; } EndPaint(hWnd, &ps); + LeaveCriticalSection(&ptrayItem->lock); } break; - case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: @@ -105,7 +110,7 @@ { MSG msg; SystrayItem *ptrayItem = systray; - + /* relay the event to the tooltip */ while ( ptrayItem ) { if (ptrayItem->hWnd == hWnd) { msg.hwnd=hWnd; @@ -121,14 +126,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) { @@ -145,6 +150,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)); } @@ -176,30 +189,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 ); @@ -217,31 +221,72 @@ ERR( "CreateWindow(TOOLTIP) failed\n" ); return FALSE; } - return TRUE; -} + /* Enter the message loop */ + while (GetMessageA (&msg, 0, 0, 0) > 0) { + TranslateMessage (&msg); + DispatchMessageA (&msg); + } -static void SYSTRAY_ItemTerm(SystrayItem *ptrayItem) -{ + TRACE("Shutting down system tray thread\n"); if(ptrayItem->notifyIcon.hIcon) DestroyIcon(ptrayItem->notifyIcon.hIcon); if(ptrayItem->hWndToolTip) DestroyWindow(ptrayItem->hWndToolTip); - if(ptrayItem->hWnd) - DestroyWindow(ptrayItem->hWnd); + + 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) +{ + /* 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); } @@ -250,8 +295,10 @@ { TTTOOLINFOA ti; + 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;