Run system tray window in dedicated thread

Mike Hearn m.hearn at signal.qinetiq.com
Mon Jun 9 10:19:46 CDT 2003


Here's the result of todays hacking for the enjoyment of all.

ChangeLog:
 - Run the system tray window in a dedicated thread to avoid potential
deadlocks.
 - Add some more comments to the code. 
 - Don't send WM_MOUSEMOVE, some apps don't seem to be expecting it and
screw up if it's sent.


-- 
Mike Hearn <m.hearn at signal.qinetiq.com>
QinetiQ - Malvern Technology Center
-------------- next part --------------
--- dlls/shell32/systray.c.old	2003-06-09 11:32:26.000000000 +0100
+++ dlls/shell32/systray.c	2003-06-09 16:16:11.000000000 +0100
@@ -1,11 +1,11 @@
 /*
- *	Systray
+ * System tray handling code (client side)
  *
- *	Copyright 1999 Kai Morich	<kai.morich at bigfoot.de>
+ * Copyright 1999 Kai Morich   <kai.morich at bigfoot.de>
+ * Copyright 2003 Mike Hearn   <mike at 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
@@ -43,15 +43,15 @@
   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
@@ -75,8 +75,8 @@
   {
     RECT rc;
     SystrayItem  *ptrayItem = systray;
-    int top, left;
-
+    int top;
+    EnterCriticalSection(&ptrayItem->lock);
     while (ptrayItem) {
       if (ptrayItem->hWnd==hWnd) {
 	if (ptrayItem->notifyIcon.hIcon) {
@@ -87,6 +87,7 @@
 	  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);
 	  }
 	}
@@ -95,10 +96,10 @@
       ptrayItem = ptrayItem->nextTrayItem;
     }
     EndPaint(hWnd, &ps);
+    LeaveCriticalSection(&ptrayItem->lock);
   }
   break;
 
-  case WM_MOUSEMOVE:
   case WM_LBUTTONDOWN:
   case WM_LBUTTONUP:
   case WM_RBUTTONDOWN:
@@ -108,7 +109,7 @@
   {
     MSG msg;
     SystrayItem *ptrayItem = systray;
-
+    /* relay the event to the tooltip */
     while ( ptrayItem ) {
       if (ptrayItem->hWnd == hWnd) {
         msg.hwnd=hWnd;
@@ -124,14 +125,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) {
@@ -179,26 +180,17 @@
 }
 
 
-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",
@@ -220,31 +212,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);
 }
 
@@ -253,9 +286,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;


More information about the wine-patches mailing list