Wednesday, January 9, 2008

Removing Orphaned System Tray Icons

The Problem My server process places an icon in the tray window. This is a nice feature, but Windows does not clean up the tray when programs crash - these icons are orphaned.

While a server process crashing is a rare event, in a development environment we constantly start and restart our processes in order to debug them. As a result, dozens (or in my case, hundreds) of icons show up in the tray, consuming a number of handles.

These icons do get cleaned up when a user moves the pointer over them. This is annoying in development, and in a production server environment could go unnoticed for a dangerously long period of time.

The Solution
In order to fix the problem, you must trick windows into thinking the user moved the mouse over the tray window. Take the following steps:

1) Get a handle to the notification area of the tray window. Of course, my friends at Microsoft change the hierarchy of windows from version to version. To determine your hierarchy, you need to run Spy+.

In my example (Windows XP SP2) the hierarchy is

Shell_TrayWnd/TrayNotifyWnd/SysPager/Notification Area

I'd be interested in hearing the other hierarchies found in the comments. Once you know your hierarchy, call FindWindowEx() to get a handle to the notification area. You'll need to start with Shell_TrayWnd, and for the others pass in the previous handle.

2) Get the window RECT for the notification area. This is done through the Windows API call GetWindowRect() using the handle from step 1.

3) Post mouse move messages to the notification area. Since you don't know where your icon is, you will need to post them across the entire window. This is done using PostMessage() with the handle to the notification area, and the message type WM_MOUSEMOVE.

Use the Y coordinate midpoint and increment my X coordinate from 0 to the width of the window. You only need to post one message to each icon, so you can increment the x coordinate by a number greater than 1 (I used 3). The coordinates are relative to the window, not the screen, so you use the width and height of the RECT from step 2.

No comments: