Hi, This patch updates my system tray patch to work with the new atom interning code, fixes a few bugs and replaces the per-icon mutex with a global one. The critical section handling should therefore be much less broken now. Yes, I know it should be in wineshell.exe but for now this will do, and the code can always be moved later. thanks -mike ChangeLog: Implement support for XEMBED based system trays Index: dlls/shell32/systray.c =================================================================== RCS file: /home/wine/wine/dlls/shell32/systray.c,v retrieving revision 1.24 diff -u -r1.24 systray.c --- dlls/shell32/systray.c 24 Oct 2003 04:23:37 -0000 1.24 +++ dlls/shell32/systray.c 30 Nov 2003 16:08:37 -0000 @@ -1,11 +1,11 @@ /* - * Systray + * System tray handling code (client side) * - * Copyright 1999 Kai Morich <kai.morich@xxxxxxxxxx> + * Copyright 1999 Kai Morich <kai.morich@xxxxxxxxxx> + * Copyright 2003 Mike Hearn <mike@xxxxxxxxxxxxx> * - * 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 @@ -42,7 +42,17 @@ #include "commctrl.h" #include "wine/debug.h" -WINE_DEFAULT_DEBUG_CHANNEL(shell); +WINE_DEFAULT_DEBUG_CHANNEL(systray); + +static CRITICAL_SECTION systray_lock; +static CRITICAL_SECTION_DEBUG critsect_debug = +{ + 0, 0, &systray_lock, + { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, + 0, 0, { 0, (DWORD)(__FILE__ ": system tray") } +}; +static CRITICAL_SECTION systray_lock = { &critsect_debug, -1, 0, 0, 0, 0 }; + typedef struct SystrayItem { HWND hWnd; @@ -51,12 +61,11 @@ 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 @@ -74,21 +83,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(&systray_lock); + while (ptrayItem) { - if (ptrayItem->hWnd==hWnd) { + 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(&systray_lock); SYSTRAY_Delete(&ptrayItem->notifyIcon); } } @@ -97,10 +111,10 @@ ptrayItem = ptrayItem->nextTrayItem; } EndPaint(hWnd, &ps); + LeaveCriticalSection(&systray_lock); } break; - case WM_MOUSEMOVE: case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_RBUTTONDOWN: @@ -110,7 +124,8 @@ { MSG msg; SystrayItem *ptrayItem = systray; - + /* relay the event to the tooltip */ + EnterCriticalSection(&systray_lock); while ( ptrayItem ) { if (ptrayItem->hWnd == hWnd) { msg.hwnd=hWnd; @@ -125,15 +140,17 @@ } ptrayItem = ptrayItem->nextTrayItem; } + LeaveCriticalSection(&systray_lock); } - /* 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 */ + EnterCriticalSection(&systray_lock); while (ptrayItem) { if (ptrayItem->hWnd == hWnd) { if (ptrayItem->notifyIcon.hWnd && ptrayItem->notifyIcon.uCallbackMessage) { @@ -147,9 +164,18 @@ } ptrayItem = ptrayItem->nextTrayItem; } + LeaveCriticalSection(&systray_lock); } 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)); } @@ -169,7 +195,7 @@ wc.hInstance = 0; wc.hIcon = 0; wc.hCursor = LoadCursorA(0, (LPSTR)IDC_ARROW); - wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wc.hbrBackground = (HBRUSH) COLOR_WINDOW; wc.lpszMenuName = NULL; wc.lpszClassName = "WineSystray"; @@ -181,30 +207,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, + "WineSystray", "Windows System Tray", + 0, CW_USEDEFAULT, CW_USEDEFAULT, rect.right-rect.left, rect.bottom-rect.top, 0, 0, 0, 0 ); @@ -222,31 +239,73 @@ 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. */ + EnterCriticalSection(&systray_lock); + if ( firstSystray ) { + firstSystray = FALSE; + if ( !SYSTRAY_RegisterClass() ) { + ERR( "RegisterClass(WineSystray) failed\n" ); + LeaveCriticalSection(&systray_lock); + return FALSE; + } + } + LeaveCriticalSection(&systray_lock); + + 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. */ + 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); return; } void SYSTRAY_ItemSetMessage(SystrayItem *ptrayItem, UINT uCallbackMessage) { + EnterCriticalSection(&systray_lock); ptrayItem->notifyIcon.uCallbackMessage = uCallbackMessage; + LeaveCriticalSection(&systray_lock); } void SYSTRAY_ItemSetIcon(SystrayItem *ptrayItem, HICON hIcon) { + EnterCriticalSection(&systray_lock); ptrayItem->notifyIcon.hIcon = CopyIcon(hIcon); + LeaveCriticalSection(&systray_lock); + InvalidateRect(ptrayItem->hWnd, NULL, TRUE); } @@ -255,9 +314,11 @@ { TTTOOLINFOA ti; - strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip)); + EnterCriticalSection(&systray_lock); + strncpy(ptrayItem->notifyIcon.szTip, szTip, sizeof(ptrayItem->notifyIcon.szTip)); ptrayItem->notifyIcon.szTip[sizeof(ptrayItem->notifyIcon.szTip)-1]=0; - + LeaveCriticalSection(&systray_lock); + ti.cbSize = sizeof(TTTOOLINFOA); ti.uFlags = 0; ti.hwnd = ptrayItem->hWnd; @@ -280,10 +341,15 @@ { SystrayItem **ptrayItem = &systray; + TRACE("%p\n", pnid); + + EnterCriticalSection(&systray_lock); /* Find last element. */ while( *ptrayItem ) { - if ( SYSTRAY_ItemIsEqual(pnid, &(*ptrayItem)->notifyIcon) ) + if ( SYSTRAY_ItemIsEqual(pnid, &(*ptrayItem)->notifyIcon) ) { + LeaveCriticalSection(&systray_lock); return FALSE; + } ptrayItem = &((*ptrayItem)->nextTrayItem); } /* Allocate SystrayItem for element and add to end of list. */ @@ -297,6 +363,7 @@ SYSTRAY_ItemSetMessage(*ptrayItem, (pnid->uFlags&NIF_MESSAGE)?pnid->uCallbackMessage:0); SYSTRAY_ItemSetTip (*ptrayItem, (pnid->uFlags&NIF_TIP) ?pnid->szTip :"", FALSE); + LeaveCriticalSection(&systray_lock); TRACE("%p: %p %s\n", (*ptrayItem), (*ptrayItem)->notifyIcon.hWnd, (*ptrayItem)->notifyIcon.szTip); return TRUE; @@ -307,8 +374,12 @@ { SystrayItem *ptrayItem = systray; + TRACE("%p\n", pnid); + + EnterCriticalSection(&systray_lock); while ( ptrayItem ) { if ( SYSTRAY_ItemIsEqual(pnid, &ptrayItem->notifyIcon) ) { + LeaveCriticalSection(&systray_lock); if (pnid->uFlags & NIF_ICON) SYSTRAY_ItemSetIcon(ptrayItem, pnid->hIcon); if (pnid->uFlags & NIF_MESSAGE) @@ -321,6 +392,7 @@ } ptrayItem = ptrayItem->nextTrayItem; } + LeaveCriticalSection(&systray_lock); return FALSE; /* not found */ } @@ -329,6 +401,9 @@ { SystrayItem **ptrayItem = &systray; + TRACE("%p\n", pnid); + + EnterCriticalSection(&systray_lock); while (*ptrayItem) { if (SYSTRAY_ItemIsEqual(pnid, &(*ptrayItem)->notifyIcon)) { SystrayItem *next = (*ptrayItem)->nextTrayItem; @@ -338,10 +413,12 @@ free(*ptrayItem); *ptrayItem = next; + LeaveCriticalSection(&systray_lock); return TRUE; } ptrayItem = &((*ptrayItem)->nextTrayItem); } + LeaveCriticalSection(&systray_lock); return FALSE; /* not found */ } Index: dlls/x11drv/event.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/event.c,v retrieving revision 1.29 diff -u -r1.29 event.c --- dlls/x11drv/event.c 21 Nov 2003 21:48:36 -0000 1.29 +++ dlls/x11drv/event.c 30 Nov 2003 16:08:37 -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 @@ -51,10 +52,15 @@ WINE_DEFAULT_DEBUG_CHANNEL(event); WINE_DECLARE_DEBUG_CHANNEL(clipboard); +WINE_DECLARE_DEBUG_CHANNEL(systray); /* X context to associate a hwnd to an X window */ extern XContext winContext; +extern Atom systray_selection; +extern Window systray_window; + + #define DndNotDnd -1 /* OffiX drag&drop */ #define DndUnknown 0 #define DndRawData 1 @@ -1213,7 +1219,47 @@ static void EVENT_ClientMessage( HWND hWnd, XClientMessageEvent *event ) { if (event->message_type != None && event->format == 32) { - if (event->message_type == x11drv_atom(WM_PROTOCOLS)) + if (event->message_type == x11drv_atom(MANAGER)) { + if (event->data.l[1] == systray_selection) { + 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 == x11drv_atom(_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 == x11drv_atom(WM_PROTOCOLS)) handle_wm_protocols_message( hWnd, event ); else if (event->message_type == x11drv_atom(DndProtocol)) { Index: dlls/x11drv/window.c =================================================================== RCS file: /home/wine/wine/dlls/x11drv/window.c,v retrieving revision 1.65 diff -u -r1.65 window.c --- dlls/x11drv/window.c 21 Nov 2003 21:48:36 -0000 1.65 +++ dlls/x11drv/window.c 30 Nov 2003 16:08:37 -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 <stdarg.h> #include <stdlib.h> +#include <stdio.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif @@ -46,6 +48,7 @@ #include "mwm.h" WINE_DEFAULT_DEBUG_CHANNEL(x11drv); +WINE_DECLARE_DEBUG_CHANNEL(systray); extern Pixmap X11DRV_BITMAP_Pixmap( HBITMAP ); @@ -73,6 +76,7 @@ "WM_PROTOCOLS", "WM_DELETE_WINDOW", "WM_TAKE_FOCUS", + "MANAGER", "KWM_DOCKWINDOW", "DndProtocol", "DndSelection", @@ -81,6 +85,9 @@ "_NET_WM_PID", "_NET_WM_PING", "_NET_WM_NAME", + "_XEMBED_INFO", + "_XEMBED", + "_NET_SYSTEM_TRAY_OPCODE", "XdndAware", "XdndEnter", "XdndPosition", @@ -103,6 +110,14 @@ "text/richtext" }; +/* for XDG systray icons */ +Atom systray_selection; +Window systray_window; +#define SYSTEM_TRAY_REQUEST_DOCK 0 +#define SYSTEM_TRAY_BEGIN_MESSAGE 1 +#define SYSTEM_TRAY_CANCEL_MESSAGE 2 + + static LPCSTR whole_window_atom; static LPCSTR client_window_atom; static LPCSTR icon_window_atom; @@ -354,11 +369,60 @@ 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); + + + /* set XEMBED protocol data on the window */ + info[0] = 0; /* protocol version */ + info[1] = 0; /* mapped = true */ + XChangeProperty(display, data->whole_window, x11drv_atom(_XEMBED_INFO), x11drv_atom(_XEMBED_INFO), 32, PropModeReplace, (unsigned char*)info, 2); + + /* send the docking request message */ + memset(&ev, 0, sizeof(ev)); + 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; + XSendEvent(display, systray_window, False, NoEventMask, &ev); + XSync(display, False); + + WIN_ReleasePtr(win); + return TRUE; +} + + /*********************************************************************** * X11DRV_set_wm_hints @@ -408,7 +472,7 @@ set_size_hints( display, win ); /* systray properties (KDE only for now) */ - if (win->dwExStyle & WS_EX_TRAYWINDOW) + if ((win->dwExStyle & WS_EX_TRAYWINDOW) && (systray_window == None)) { int val = 1; XChangeProperty( display, data->whole_window, x11drv_atom(KWM_DOCKWINDOW), @@ -709,10 +773,19 @@ static void create_desktop( Display *display, WND *wndPtr ) { X11DRV_WND_DATA *data = wndPtr->pDriverData; - + char *systray_buffer; + wine_tsx11_lock(); winContext = XUniqueContext(); XInternAtoms( display, (char **)atom_names, NB_XATOMS - FIRST_XATOM, False, X11DRV_Atoms ); + + /* we can't intern this with the rest as it depends on the screen we are connecting to */ + systray_buffer = HeapAlloc(GetProcessHeap(), 0, sizeof(char)*20); + sprintf(systray_buffer, "_NET_SYSTEM_TRAY_S%d", DefaultScreen(display)); + systray_selection = XInternAtom(display, systray_buffer, False); + HeapFree(GetProcessHeap(), 0, systray_buffer); + + wine_tsx11_unlock(); whole_window_atom = MAKEINTATOMA( GlobalAddAtomA( "__wine_x11_whole_window" )); @@ -1109,6 +1182,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 */ + systray_window = XGetSelectionOwner(display, systray_selection); + if (systray_window != None) + X11DRV_systray_dock_window(hwnd, display); + } + WIN_ReleaseWndPtr( wndPtr ); return TRUE; Index: dlls/x11drv/x11drv.h =================================================================== RCS file: /home/wine/wine/dlls/x11drv/x11drv.h,v retrieving revision 1.11 diff -u -r1.11 x11drv.h --- dlls/x11drv/x11drv.h 25 Nov 2003 03:27:38 -0000 1.11 +++ dlls/x11drv/x11drv.h 30 Nov 2003 16:08:37 -0000 @@ -387,6 +387,7 @@ XATOM_WM_PROTOCOLS, XATOM_WM_DELETE_WINDOW, XATOM_WM_TAKE_FOCUS, + XATOM_MANAGER, XATOM_KWM_DOCKWINDOW, XATOM_DndProtocol, XATOM_DndSelection, @@ -395,6 +396,9 @@ XATOM__NET_WM_PID, XATOM__NET_WM_PING, XATOM__NET_WM_NAME, + XATOM__XEMBED_INFO, + XATOM__XEMBED, + XATOM__NET_SYSTEM_TRAY_OPCODE, XATOM_XdndAware, XATOM_XdndEnter, XATOM_XdndPosition,