How Windows 95 Handled System Tray Mouse Clicks

In Windows 95, the system tray—officially named the notification area—relied on a callback-driven messaging architecture to communicate user interactions back to applications. When a user clicked or double-clicked an icon, the Windows shell captured the raw hardware mouse event and redirected it as an application-defined window message to the application's message loop, where it had to be unpacked and handled.

The Shell_NotifyIcon Interface

Applications integrated with the system tray using the Win32 Shell API function Shell_NotifyIcon, passing the NIM_ADD message along with a pointer to a NOTIFYICONDATA structure. This structure established the communication link between the Windows 95 taskbar process (explorer.exe) and the host application.

Key fields in NOTIFYICONDATA included:

  • hWnd: The handle to the window designated to receive notifications.
  • uID: An application-defined identifier for the specific icon.
  • uCallbackMessage: A custom window message identifier (typically defined using WM_USER + n or WM_APP + n).

By configuring uCallbackMessage, the developer instructed the shell to send that custom message to hWnd whenever mouse activity occurred over the icon's designated coordinate space.

Message Forwarding and Parameter Encoding

When a user moved the mouse over the icon, clicked it, or double-clicked it, the taskbar captured the event and sent the specified uCallbackMessage directly to the registered hWnd. The shell passed the interaction details inside the standard Win32 message parameters:

  • wParam: Contained the uID of the notification icon, allowing a single window procedure to differentiate between multiple tray icons owned by the same application.
  • lParam: Contained the underlying mouse event constant, such as WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONUP, or WM_LBUTTONDBLCLK.

Processing Click and Double-Click Sequences

In Windows 95, double-clicks are not isolated events; they are part of a continuous sequence. When a user double-clicked an icon, the application received the messages in the following order:

  1. WM_LBUTTONDOWN
  2. WM_LBUTTONUP
  3. WM_LBUTTONDBLCLK
  4. WM_LBUTTONUP

Because WM_LBUTTONUP fired on the initial click before WM_LBUTTONDBLCLK arrived, applications designed to support both single-click and double-click behaviors had to handle the timing carefully. Executing an action immediately on WM_LBUTTONUP could result in unwanted behavior if the user intended to double-click.

To manage this, developers used two primary implementation patterns:

  • Distinct Action Allocation: Many applications avoided conflicting actions by reserving the left double-click for opening the primary interface (e.g., maximizing the window or launching properties) while reserving single clicks for other purposes, such as displaying a popup menu on WM_RBUTTONUP (right-click).
  • Timer-Based Delay: If an application required separate actions for single and double left-clicks, it set a system timer via SetTimer on the first WM_LBUTTONUP matching the duration returned by GetDoubleClickTime(). If WM_LBUTTONDBLCLK arrived before the timer expired, the timer was cancelled via KillTimer and the double-click handler executed. If the timer elapsed without a double-click arriving, the application executed the single-click routine.