First stab at NETWM system tray support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Known issues:

* It is ugly. I need to implement XSETTINGS
support so the background colour of the trayicon matches that of the
panel. This will only work on GNOME, unfortunately KDE doesn't implement
it yet.

* It is buggy. Having said that, while I'm sure my code isn't perfect,
the gnome tray applet seems rather buggy as well. I've seen apps be
given 1px allocations before, this happens to Wine sometimes. Also, for
some reason it occasionally fails to dock.

* It doesn't dock when a new tray applet is added. It can detect this,
and will TRY to dock, but due to either a bug in my code or Anders', the
dock fails.

* We still fall back to the old KDE protocol, which is a bit sucKy. In
particular there seems to be no way to figure out if the dock succeeded,
you just have to hope that the user is using KDE. At some point this
code needs to be deprecated, as otherwise there is no way to do things
for people with no system tray applet at all (many people).

* Long term Wine needs to be able to open its own "tray icon window" if
none is present in the system.

With that out the way, here is the patch.

ChangeLog:
Implement basic NETWM system tray support for Gnome2 and KDE 3.1
Index: dlls/x11drv/window.c
===================================================================
RCS file: /home/wine/wine/dlls/x11drv/window.c,v
retrieving revision 1.53
diff -u -r1.53 window.c
--- dlls/x11drv/window.c	19 May 2003 19:08:57 -0000	1.53
+++ dlls/x11drv/window.c	4 Jun 2003 21:41:05 -0000
@@ -24,6 +24,7 @@
 
 #include <stdlib.h>
 #include <unistd.h>
+#include <stdio.h>
 
 #include "ts_xlib.h"
 #include <X11/Xresource.h>
@@ -42,6 +43,7 @@
 #include "mwm.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(x11drv);
+WINE_DECLARE_DEBUG_CHANNEL(systray);
 
 extern Pixmap X11DRV_BITMAP_Pixmap( HBITMAP );
 
@@ -55,6 +57,7 @@
 Atom wmProtocols = None;
 Atom wmDeleteWindow = None;
 Atom wmTakeFocus = None;
+Atom wmManager = None;
 Atom dndProtocol = None;
 Atom dndSelection = None;
 Atom wmChangeState = None;
@@ -62,12 +65,28 @@
 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 MAX_TRAY_ICONS 256
+int undockedTrayIconsCount = 0;
+static HWND undockedTrayIcons[MAX_TRAY_ICONS]; /* stores HWNDs of undocked tray windows when a
+						  NETWM tray window appears, we can dock */
+
+#define SYSTEM_TRAY_REQUEST_DOCK    0
+#define SYSTEM_TRAY_BEGIN_MESSAGE   1
+#define SYSTEM_TRAY_CANCEL_MESSAGE  2
+
+
 /***********************************************************************
  *		is_window_managed
  *
@@ -315,11 +334,79 @@
             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
+ */
+void X11DRV_systray_dock_window( Display *display, HWND hwnd ) {
+  WND* win = WIN_GetPtr(hwnd);
+  struct x11drv_win_data *data = win->pDriverData;  
+  XEvent ev;
+  Window child;
+  XWindowAttributes attrs;
+  int x, y;
+  RECT r;
+  unsigned long info[2];
+
+  TRACE("Docking tray icon 0x%x\n", hwnd);
+  
+  /* set XEMBED protocol data on the window */
+  info[0] = 0; /* protocol version */
+  info[1] = 1; /* 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);
+
+  /* now set sync the X11 window rects to the win32 window rects,
+     so the shell tray code can properly align the icon */
+  
+  wine_tsx11_lock();
+  XTranslateCoordinates(display, data->whole_window, root_window,
+			0, 0, &x, &y, &child);
+  XGetWindowAttributes(display, data->whole_window, &attrs);
+  wine_tsx11_unlock();
+
+  r.left = x;    
+  r.top = y;
+  r.right = r.left + attrs.width;
+  r.bottom = r.top + attrs.height;
+
+  WIN_SetRectangles(hwnd, &r, &r);
+  WIN_ReleasePtr(win);
+}
+
+/***********************************************************************
+ *              X11DRV_systray_dock_window
+ *
+ * Docks all undocked tray icons. Usually called when a tray manager appears.
+ */
+void X11DRV_systray_dock_all(Display* display) {
+  TRACE_(systray)("called with %d undocked tray icon(s)\n", undockedTrayIconsCount);
+  while (undockedTrayIconsCount > 0) {
+    X11DRV_systray_dock_window(display, undockedTrayIcons[0]);
+    undockedTrayIconsCount--;
+  }
+}
 
 /***********************************************************************
  *              X11DRV_set_wm_hints
@@ -365,16 +452,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 */
@@ -403,7 +493,8 @@
         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;
-
+	/* if (win->dwExStyle & WS_EX_TRAYWINDOW) mwm_hints.decorations = 0; */
+	
         XChangeProperty( display, data->whole_window, mwmHints, mwmHints, 32,
                          PropModeReplace, (char*)&mwm_hints, sizeof(mwm_hints)/sizeof(long) );
     }
@@ -648,10 +739,11 @@
 /**********************************************************************
  *		create_desktop
  */
-static void create_desktop( Display *display, WND *wndPtr )
+static void create_desktop( Display *display, WND *wndPtr, CREATESTRUCTA *cs )
 {
     X11DRV_WND_DATA *data = wndPtr->pDriverData;
-
+    char* buffer;
+    
     wine_tsx11_lock();
     winContext     = XUniqueContext();
     wmProtocols    = XInternAtom( display, "WM_PROTOCOLS", False );
@@ -665,6 +757,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" ));
@@ -678,7 +783,10 @@
     SetPropA( wndPtr->hwndSelf, client_window_atom, (HANDLE)root_window );
     SetPropA( wndPtr->hwndSelf, "__wine_x11_visual_id", (HANDLE)XVisualIDFromVisual(visual) );
 
+    SendMessageW( wndPtr->hwndSelf, WM_NCCREATE, 0, (LPARAM)cs );
     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 +880,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 ),
@@ -936,7 +1045,7 @@
 
     if (!wndPtr->parent)
     {
-        create_desktop( display, wndPtr );
+        create_desktop( display, wndPtr, cs );
         WIN_ReleasePtr( wndPtr );
         return TRUE;
     }
@@ -961,7 +1070,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;
 
@@ -1056,7 +1165,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*/
@@ -1076,6 +1184,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(display, hwnd);
+    }
     WIN_ReleaseWndPtr( wndPtr );
     return TRUE;
 
Index: dlls/x11drv/event.c
===================================================================
RCS file: /home/wine/wine/dlls/x11drv/event.c,v
retrieving revision 1.20
diff -u -r1.20 event.c
--- dlls/x11drv/event.c	19 May 2003 19:07:32 -0000	1.20
+++ dlls/x11drv/event.c	4 Jun 2003 21:41:06 -0000
@@ -34,6 +34,7 @@
 
 #include <assert.h>
 #include <string.h>
+#include <stdio.h>
 #include "wine/winuser16.h"
 #include "shlobj.h"  /* DROPFILES */
 
@@ -47,15 +48,21 @@
 #include "wine/debug.h"
 
 WINE_DEFAULT_DEBUG_CHANNEL(event);
+WINE_DECLARE_DEBUG_CHANNEL(systray);
 
 /* X context to associate a hwnd to an X window */
 extern XContext winContext;
 
 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
@@ -105,6 +112,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;
@@ -165,9 +173,6 @@
         return WaitForMultipleObjectsEx( count, handles, flags & MWMO_WAITALL,
                                          timeout, flags & MWMO_ALERTABLE );
 
-    /* check whether only server queue handle was passed in */
-    if (count < 2) flags &= ~MWMO_WAITALL;
-
     for (i = 0; i < count; i++) new_handles[i] = handles[i];
     new_handles[count] = data->display_fd;
 
@@ -275,7 +280,8 @@
 
   if ( !hWnd && event->xany.window != root_window
              && event->type != PropertyNotify
-             && event->type != MappingNotify)
+             && 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 +344,6 @@
       break;
 
     case ClientMessage:
-      if (!hWnd) return;
       EVENT_ClientMessage( hWnd, (XClientMessageEvent *) event );
       break;
 
@@ -1431,8 +1436,39 @@
  */
 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]);
+	    if (systrayWindow == None) {
+	      /* only update when there was no previous tray manager, if it's just a replacement ignore the event */
+	      systrayWindow = event->data.l[2];
+	      TRACE_(systray)("docking all to %d\n", (int)systrayWindow);
+	      X11DRV_systray_dock_all(event->display);
+	    }
+	}
+    } 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/shell32/systray.c
===================================================================
RCS file: /home/wine/wine/dlls/shell32/systray.c,v
retrieving revision 1.20
diff -u -r1.20 systray.c
--- dlls/shell32/systray.c	24 Nov 2002 22:16:29 -0000	1.20
+++ dlls/shell32/systray.c	4 Jun 2003 21:41:07 -0000
@@ -37,7 +37,7 @@
 #include "commctrl.h"
 #include "wine/debug.h"
 
-WINE_DEFAULT_DEBUG_CHANNEL(shell);
+WINE_DEFAULT_DEBUG_CHANNEL(systray);
 
 typedef struct SystrayItem {
   HWND                  hWnd;
@@ -75,13 +75,16 @@
   {
     RECT rc;
     SystrayItem  *ptrayItem = systray;
-
+    int top, left;
+    
     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 and left 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);
 	    SYSTRAY_Delete(&ptrayItem->notifyIcon);

[Index of Archives]     [Gimp for Windows]     [Red Hat]     [Samba]     [Yosemite Camping]     [Graphics Cards]     [Wine Home]

  Powered by Linux