How Windows 95 Handled System Tray Notifications

When Windows 95 introduced the Taskbar, it included a dedicated notification area—commonly known as the System Tray—to allow background processes to display status indicators and receive user input without cluttering the screen with full application windows. This article provides an overview of the Win32 architecture behind this mechanism, detailing how background programs registered icons with the Windows Shell, received user interaction events through custom window messages, and managed system resources while running silently.

The Notification Area Architecture

The System Tray was integrated directly into Explorer.exe, the process responsible for the Windows 95 desktop and Taskbar. Because 32-bit Windows 95 applications ran in their own preemptively multitasked address spaces, a background program could not directly draw into the Taskbar's screen area. Instead, the operating system relied on a centralized Win32 Shell API to bridge background workers with the shell interface.

The Shell_NotifyIcon Function

Background programs interfaced with the notification area through a single API function: Shell_NotifyIcon.

The function accepted two parameters:

  1. A message command flag: Specifying the desired action, such as NIM_ADD (to create an icon), NIM_MODIFY (to change the icon image or tooltip), or NIM_DELETE (to remove the icon when the task terminated).
  2. A pointer to a NOTIFYICONDATA structure: Containing the configuration details for the entry.

The NOTIFYICONDATA structure held critical metadata, including:

  • cbSize: The size of the structure.
  • hWnd: The handle to the window that would receive notification events.
  • uID: An application-defined identifier for the specific icon.
  • uFlags: Flags determining which other members contained valid data (NIF_MESSAGE, NIF_ICON, NIF_TIP).
  • uCallbackMessage: A custom message ID defined by the application.
  • hIcon: The handle to the 16x16 icon to render.
  • szTip: A string buffer for the mouse-hover tooltip text (limited to 64 characters in Windows 95).

The Asynchronous Message Loop

To manage tray notifications, a background task did not need an active, visible user interface, but it did require an internal message queue and a hidden window.

When a program initialized, it created an invisible window to secure an hWnd and entered a standard message retrieval loop (GetMessage/DispatchMessage). When calling Shell_NotifyIcon with NIM_ADD, the application provided this hWnd alongside an application-defined message constant (typically within the WM_USER or WM_APP range).

Once the icon was added, Explorer handled the drawing and hovered tooltips. When a user interacted with the icon—such as clicking, double-clicking, or right-clicking—Explorer identified the corresponding task and forwarded a notification back to the application's hidden window using the registered callback message:

  • wParam: Contained the uID of the icon that triggered the event, allowing one window to manage multiple tray icons.
  • lParam: Contained the specific mouse message that occurred (such as WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, or WM_RBUTTONUP).

Processing User Interaction

When the background task's window procedure (WndProc) intercepted its custom uCallbackMessage, it evaluated lParam to determine how to respond:

  • Left Click or Double-Click: Applications typically brought their main interface to the foreground, unhiding their window and calling SetForegroundWindow.
  • Right Click: Programs typically generated a context menu by calling CreatePopupMenu, appending menu options, and displaying it with TrackPopupMenu. This enabled actions like "Open", "Properties", or "Exit" without exposing a full interface.

By decoupling the display rendering (handled by Explorer) from the event handling (processed by the background task's own message queue), Windows 95 ensured that background services could remain responsive and maintain a minimal memory footprint while giving users direct control over background execution.