//--------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1991-1992
//
// File:      desktop.c
//
// Contains:  Shell desktop functions.
//
// History:
//  12-28-92 SatoNa     Calls FileIconIndex().
//
//---------------------------------------------------------------------------
#include "cabinet.h"
#include "cabwnd.h"
#include "rcids.h"
#include <shellapi.h>
#include "trayclok.h"
#include <fsmenu.h>
#include <shguidp.h>
#include <trayp.h>
#include "traynot.h"
#include "help.h"       // help ids

#if defined(FE_IME)
#include <immp.h>
#endif

#include <regstr.h>

#ifndef WINNT       // BUGBUG - Fix this when NT gets POWERMGT features
#include <vmm.h>
#include <bios.h>
#define NOPOWERSTATUSDEFINES
#include <pwrioctl.h>
#include <wshioctl.h>
#include <dbt.h>
#include <pbt.h>

#define Not_VxD
#define No_CM_Calls
#include <configmg.h>
#endif

#define IsEjectAllowed() (g_ts.hBIOS != INVALID_HANDLE_VALUE)

#define ENTERCRITICAL   MEnterCriticalSection(&g_csThreads)
#define LEAVECRITICAL   MLeaveCriticalSection(&g_csThreads)

#define POLLINTERVAL    (15*1000)       // 15 seconds
#define MINPOLLINTERVAL (3*1000)        // 3 seconds
#define ERRTIMEOUT      (5*1000)        // 5 seconds

#define SECOND 1000
#define RUNWAITSECS 5

#ifdef WINNT
#define MAXPRINTERBUFFER (MAX_PATH+18+1)
#define JOB_STATUS_ERROR_BITS (JOB_STATUS_USER_INTERVENTION|JOB_STATUS_ERROR)
#else
#define MAXPRINTERBUFFER 32
#define JOB_STATUS_ERROR_BITS JOB_STATUS_USER_INTERVENTION
#endif

#define TM_RELAYPOSCHANGED  (WM_USER + 0x150)
#define TM_INVALIDATEREBUILDMENU (WM_USER + 0x151)
#define TM_BRINGTOTOP       (WM_USER + 0x152)

#define RDM_ACTIVATE            (WM_USER + 0x101)

#if !defined(_PPC_) // PPC compiler generates random 4701s
#pragma warning(3 : 4700 4701 4702)
#endif

// DSA haPrinterNames structure:
typedef struct _PRINTERNAME {
    TCHAR szPrinterName[MAXPRINTERBUFFER];
    LPITEMIDLIST pidlPrinter; // relative pidl to printer
    BOOL fInErrorState;
} PRINTERNAME, * LPPRINTERNAME;

void PrintNotify_Init(HWND hWnd);
void PrintNotify_AddToQueue(LPHANDLE phaPrinterNames, LPSHELLFOLDER *ppsf, LPCITEMIDLIST pidlPrinter);
BOOL PrintNotify_StartThread(HWND hWnd);
void PrintNotify_HandleFSNotify(HWND hWnd, LPCITEMIDLIST *ppidl, LONG lEvent);
void PrintNotify_Exit(void);
void PrintNotify_IconNotify(HWND hWnd, UINT uMsg);

STDMETHODIMP CShellTray_AddViewPropertySheetPages(IShellBrowser * psb, DWORD dwReserved, LPFNADDPROPSHEETPAGE lpfn, LPARAM lParam);
STDMETHODIMP ShellDesktop_BrowseObject(LPSHELLBROWSER psb, LPCITEMIDLIST pdil, UINT wFlags);
void Tray_ScreenSizeChange(HWND hWnd);
void Tray_SizeWindows();
void  Tray_ContextMenu(PFileCabinet pfc, DWORD dwPos, BOOL fSetTime);
void StuckSizeChange();
void StartMenu_Build(PFileCabinet pfc);
void SetWindowStyleBit(HWND hwnd, DWORD dwBit, DWORD dwValue);
LRESULT Tray_HandleInitMenuPopup(HMENU hmenuPopup);
HMENU Menu_FindSubMenuByFirstID(HMENU hmenu, UINT id);
void CStartDropTarget_Register(LPFileCabinet pfc);
void CStartDropTarget_Revoke(LPFileCabinet pfc);
void DestroySavedWindowPositions();
void RebuildEntireMenu(PFileCabinet pfc);
void Cabinet_InitGlobalMetrics(WPARAM, LPTSTR);
void StartMenuFolder_ContextMenu(PFileCabinet pfc, DWORD dwPos);
void Tray_HandleSizing(PFileCabinet pfc, UINT code, LPRECT lprc, UINT uStuckPlace);
void MinimizeAll(HWND hwndView);
void RegisterGlobalHotkeys();
void TrayUnhide();
int HotkeyList_Restore(HWND hwnd);
LRESULT Tray_RegisterHotkey(HWND hwnd, int i);
void CheckWinIniForAssocs(void);
void SetAutoHideTimer();

#if (defined(DBCS) || defined(FE_IME))
// b#11258-win95d
#if !defined(WINNT)
DWORD WINAPI ImmGetAppIMECompatFlags(DWORD dwThreadID);
#endif
#endif


// appbar stuff
BOOL IAppBarSetAutoHideBar(HWND hwnd, BOOL fSet, UINT uEdge);
void IAppBarActivationChange(HWND hWnd, UINT uEdge);
HWND g_hwndAutoHide[ABE_MAX] = { NULL, NULL, NULL, NULL };

#define ClockCtl_HandleTrayHide(fHiding) SendMessage(g_ts.hwndNotify, TNM_TRAYHIDE, 0, fHiding)

// dyna-res change for multi-config hot/warm-doc
void HandleDisplayChange(int x, int y, BOOL fCritical);
BOOL IsDisplayChangeSafe(void);
DWORD GetMinDisplayRes(void);

// appbar stuff
void AppBarNotifyAll(UINT uMsg, HWND hwndExclude, LPARAM lParam);

HWND v_hwndTray = NULL;
PFileCabinet g_pfcTray = NULL; // we should move all the other globals into here
UINT g_cfIDList = 0;
// Button subclass.
WNDPROC g_ButtonProc = NULL;

// desktop and tray stuff
#define tray_cxScreen (g_cxScreen + g_cxEdge)
#define tray_cyScreen (g_cyScreen + g_cyEdge)
#define tray_xScreen (-g_cxEdge)
#define tray_yScreen (-g_cyEdge)

// timer IDs
#define IDT_AUTOHIDE            2
#define IDT_AUTOUNHIDE          3
#define IDT_FAVOURITE           4
#ifdef DELAYWININICHANGE
#define IDT_DELAYWININICHANGE   5
#endif
#define IDT_DESKTOP             6
#define IDT_PROGRAMS    IDM_PROGRAMS
#define IDT_RECENT      IDM_RECENT
#define IDT_REBUILDMENU         7

#define RECTWIDTH(rc)   ((rc).right-(rc).left)
#define RECTHEIGHT(rc)  ((rc).bottom-(rc).top)

extern HWND g_hwndDde;
int g_cyTrayBorders = -1; // the amount of Y difference between the window and client height;

//
// amount of time to show/hide the tray
// to turn sliding off set these to 0
//
int g_dtSlideHide;
int g_dtSlideShow;

typedef enum
{
    SMCT_NONE                   = 0x0,
    SMCT_PRIMARY                = 0x1,
    SMCT_WININIASSOCS           = 0x2,
    SMCT_BUILDLISTOFPATHS       = 0x3,
    SMCT_INITPROGRAMS           = 0x4,
    SMCT_INITRECENT             = 0x5,
    SMCT_PARTFILLPROGRAMS       = 0x6,
    SMCT_FILLRECENT             = 0x7,
    SMCT_FILLPROGRAMS           = 0x8,
    SMCT_FILLFAVOURITE          = 0x9,
    SMCT_DESKTOPHOTKEYS         = 0xa,
    SMCT_DONE                   = 0xb,
    SMCT_FILLRECENTONLY         = 0xc,
    SMCT_FILLPROGRAMSONLY       = 0xd,
    SMCT_DESKTOPHOTKEYSONLY     = 0xe,
    SMCT_STOP                   = 0xf,
    SMCT_RESTART                = 0x10
} STARTMENUCONTROLTHREAD;


enum
{
    GHID_RUN = 0,
    GHID_MINIMIZEALL,
    GHID_UNMINIMIZEALL,
    GHID_HELP,
    GHID_EXPLORER,
    GHID_FINDFILES,
    GHID_FINDCOMPUTER,
    GHID_TASKTAB,
    GHID_TASKSHIFTTAB,
    GHID_SYSPROPERTIES,
    GHID_MAX
};

const DWORD GlobalKeylist[] =
{ MAKELONG(TEXT('R'), MOD_WIN),
      MAKELONG(TEXT('M'), MOD_WIN),
      MAKELONG(TEXT('M'), MOD_SHIFT|MOD_WIN),
      MAKELONG(VK_F1,MOD_WIN),
      MAKELONG(TEXT('E'),MOD_WIN),
      MAKELONG(TEXT('F'),MOD_WIN),
      MAKELONG(TEXT('F'), MOD_CONTROL|MOD_WIN),
      MAKELONG(VK_TAB, MOD_WIN),
      MAKELONG(VK_TAB, MOD_WIN|MOD_SHIFT),
      MAKELONG(VK_PAUSE,MOD_WIN)
};


void DoExitWindows(HWND hwnd);
void TrayCommand(PFileCabinet pfc, UINT idCmd);
BOOL SetAutoHideState(BOOL fAutoHide);

LRESULT CALLBACK TrayWndProc(HWND, UINT, WPARAM, LPARAM);

//---------------------------------------------------------------------------
// Global to this file only.
// NB None of the tray stuff needs thread serialising because
// we only ever have one tray.

TRAYSTUFF g_ts = {0};

// g_fDockingFlags bits
#define DOCKFLAG_WARMEJECTABLENOW       (1<<0)

// TVSD Flags.
#define TVSD_NULL               0x0000
#define TVSD_TOPMOST            0x0002
#define TVSD_SMSMALLICONS       0x0004
#define TVSD_HIDECLOCK          0x0008

#define DM_IANELHK DM_TRACE

// Define a 16/32 bit independant format for saving the tray information
// into
typedef struct _TVSD // Tray View Size (or save) Data
{
    DWORD   dwSize;

    // Screen size in X direction.
    LONG    cxScreen;
    LONG    cyScreen;

    // Only save Widths and heights for the four edges not whole values
    LONG    dxLeft;
    LONG    dxRight;
    LONG    dyTop;
    LONG    dyBottom;

    // Additional data for AutoHide
    DWORD   uAutoHide;
    RECTL   rclAutoHide;

    // More stuff.
    DWORD   uStuckPlace;
    DWORD   dwFlags;
} TVSD;

#define IDC_FOLDERTABS 1

BOOL CreateClockWindow(HWND hwnd)
{
    g_ts.hwndNotify = TrayNotifyCreate(hwnd, IDC_CLOCK, hinstCabinet);
    return (BOOL)g_ts.hwndNotify;
}

BOOL InitTrayClass(HINSTANCE hInstance)
{
    WNDCLASS wc;

    wc.lpszClassName = c_szTrayClass;
    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = TrayWndProc;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_3DFACE+1);
    wc.lpszMenuName  = NULL;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = SIZEOF(PFileCabinet);

    return RegisterClass(&wc);
}


#define CXGAP 4
//----------------------------------------------------------------------------
// Compose the Start bitmap out of a flag and some text.
// REVIEW UNDONE - Put the up/down arrow back in.
HBITMAP CreateStartBitmap(HWND hwndTray)
{
    HBITMAP hbmpStart = NULL;
    HBITMAP hbmpStartOld = NULL;
    HICON hiconFlag = NULL;
    int cx, cy, cySmIcon;
    TCHAR szStart[CCHSZNORMAL];
    SIZE size;
    HDC hdcStart;
    HDC hdcScreen;
    HFONT hfontStart = NULL;
    HFONT hfontStartOld = NULL;
    NONCLIENTMETRICS ncm;
    RECT rcStart;

    // DebugMsg(DM_TRACE, "c.csb: Creating start bitmap.");

    hdcScreen = GetDC(NULL);
    hdcStart = CreateCompatibleDC(hdcScreen);
    if (hdcStart)
    {
        ncm.cbSize = SIZEOF(ncm);
        if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SIZEOF(ncm), &ncm, FALSE))
        {
            ncm.lfCaptionFont.lfWeight = FW_BOLD;
            hfontStart = CreateFontIndirect(&ncm.lfCaptionFont);
            hfontStartOld = SelectObject(hdcStart , hfontStart);
        }
        else
        {
            DebugMsg(DM_ERROR, TEXT("c.csb: Can't create font."));
        }
        // Get an idea about how big we need everyhting to be.
        LoadString(hinstCabinet, IDS_START, szStart, ARRAYSIZE(szStart));
        GetTextExtentPoint(hdcStart, szStart, lstrlen(szStart), &size);
// Mimick the tray and ignore the font size for determining the height.
#if 0
        cy = max(g_cySize, size.cy);
#else
        cy = g_cySize;
#endif
        cySmIcon = GetSystemMetrics(SM_CYSMICON);
        cx = (cy + size.cx) + CXGAP;
        hbmpStart = CreateCompatibleBitmap(hdcScreen, cx, cy);
        hbmpStartOld = SelectObject(hdcStart, hbmpStart);
        rcStart.left = -g_cxEdge; // subtract this off because drawcaptiontemp adds it on
        rcStart.top = 0;
        rcStart.right = cx;
        rcStart.bottom = cy;
        hiconFlag = (HICON)LoadImage(NULL, MAKEINTRESOURCE(OIC_WINLOGO_DEFAULT), IMAGE_ICON, cySmIcon, cySmIcon, 0);
        // Get User to draw everything for us.
        DrawCaptionTemp(hwndTray, hdcStart, &rcStart, hfontStart, hiconFlag, szStart, DC_INBUTTON | DC_TEXT | DC_ICON | DC_NOSENDMSG);
        // Clean up Start stuff.
        SelectObject(hdcStart, hbmpStartOld);
        if (hfontStart)
        {
            SelectObject(hdcStart, hfontStartOld);
            DeleteObject(hfontStart);
        }
        DeleteDC(hdcStart);
        DestroyIcon(hiconFlag);
    }
    else
    {
        DebugMsg(DM_ERROR, TEXT("c.csb: Can't create Start bitmap."));
    }

    ReleaseDC(NULL, hdcScreen);
    return hbmpStart;
}

VOID _GetSaveStateAndInitRects()
{
    TVSD tvsd;
    DWORD cbData = SIZEOF(tvsd);

    if (Reg_GetStruct(g_hkeyExplorer, c_szCabinetStuckRects, c_szSettings, &tvsd, &cbData) &&
        (cbData == SIZEOF(tvsd)) && (tvsd.dwSize == SIZEOF(tvsd)))
    {
        // Now lets validate the information that was returned
        if ((tvsd.dyTop < 0) || tvsd.dyTop > g_cyScreen / 2)
            tvsd.dyTop = g_ts.sizeStart.cy + (g_cyDlgFrame + g_cxBorder) * 2;

        if ((tvsd.dyBottom < 0) || tvsd.dyBottom > g_cyScreen / 2)
            tvsd.dyBottom = g_ts.sizeStart.cy + (g_cyDlgFrame + g_cxBorder) * 2;

        if ((tvsd.dxLeft < 0) || tvsd.dxLeft > g_cxScreen / 2)
            tvsd.dxLeft = g_ts.sizeStart.cx + (g_cxDlgFrame + g_cyBorder) * 2;

        if ((tvsd.dxRight < 0) || tvsd.dxRight > g_cxScreen / 2)
            tvsd.dxRight = g_ts.sizeStart.cx + (g_cxDlgFrame + g_cyBorder) * 2;

        // Restore the autohide rectangles and settings also.
        g_ts.uAutoHide = (UINT)tvsd.uAutoHide;
        g_ts.rc.left = (int)tvsd.rclAutoHide.left;
        g_ts.rc.right = (int)tvsd.rclAutoHide.right;
        g_ts.rc.top = (int)tvsd.rclAutoHide.top;
        g_ts.rc.bottom = (int)tvsd.rclAutoHide.bottom;
        g_ts.uStuckPlace = (UINT)tvsd.uStuckPlace;
        g_ts.fAlwaysOnTop = tvsd.dwFlags & TVSD_TOPMOST ? 1 : 0;
        g_ts.fSMSmallIcons = tvsd.dwFlags & TVSD_SMSMALLICONS ? 1 : 0;
        g_ts.fHideClock = tvsd.dwFlags & TVSD_HIDECLOCK ? 1 : 0;
    }
    else
    {
        // setup for default rects, when it failed.
        tvsd.cxScreen = g_cxScreen;
        tvsd.cyScreen = g_cyScreen;

        // Now get the sizes for the four edges.
        tvsd.dyTop = tvsd.dyBottom = g_ts.sizeStart.cy +
            (g_cyDlgFrame + g_cxBorder) * 2;
        tvsd.dxLeft = tvsd.dxRight = g_ts.sizeStart.cx +
            (g_cxDlgFrame + g_cyBorder) * 2;

        // this is just a sanity check.  if there were
        // no settings, then auto-hide is off, and
        // this rect will be initialized if/when autohide is turned on
        // (unless we have a bug....)
        g_ts.rc.left = 0;
        g_ts.rc.right = g_cxScreen;
        g_ts.rc.bottom = g_cyScreen;
        g_ts.rc.top = g_cyScreen - 20;

        // the ever changing default
        g_ts.uStuckPlace = STICK_BOTTOM;
        g_ts.fAlwaysOnTop = TRUE;
        g_ts.fHideClock = FALSE;
    }

    // Now set the rects to match the sizes that are in our save record
    GetWindowRect(GetDesktopWindow(), &g_ts.arStuckRects[STICK_TOP]);
    InflateRect(&g_ts.arStuckRects[STICK_TOP], g_cxEdge, g_cyEdge);
    g_ts.arStuckRects[STICK_BOTTOM] = g_ts.arStuckRects[STICK_TOP];
    g_ts.arStuckRects[STICK_LEFT] = g_ts.arStuckRects[STICK_TOP];
    g_ts.arStuckRects[STICK_RIGHT] = g_ts.arStuckRects[STICK_TOP];

    g_ts.arStuckRects[STICK_TOP].bottom = (int)tray_yScreen + tvsd.dyTop;
    g_ts.arStuckRects[STICK_BOTTOM].top    = tray_cyScreen - (int)tvsd.dyBottom;
    g_ts.arStuckRects[STICK_LEFT].right  = (int)tray_xScreen + tvsd.dxLeft;
    g_ts.arStuckRects[STICK_RIGHT].left   = tray_cxScreen - (int)tvsd.dxRight;

    // See if the screen resolution changed
    if ((g_cxScreen != tvsd.cxScreen) || (g_cyScreen != tvsd.cyScreen))
        Tray_ScreenSizeChange(NULL);
}


/*------------------------------------------------------------------
** align toolbar so that buttons are flush with client area
** and make toolbar's buttons to be MENU style
**------------------------------------------------------------------*/
void AlignStartButton(HWND hwndStart)
{
    if (hwndStart)
    {
        BITMAP bm;
        HBITMAP hbm = (HBITMAP)SendMessage(hwndStart, BM_GETIMAGE, IMAGE_BITMAP, 0);

        if (hbm)
        {
            GetObject(hbm, SIZEOF(bm), &bm);

            g_ts.sizeStart.cx = bm.bmWidth  + 2 * g_cxEdge;
            g_ts.sizeStart.cy = bm.bmHeight + 2 * g_cyEdge;
            if (g_ts.sizeStart.cy < g_cySize + 2*g_cyEdge)
                g_ts.sizeStart.cy = g_cySize + 2*g_cyEdge;
        }
        else
        {
            // BUGBUG: New user may have caused this to fail...
            // Setup some size for it that wont be too bad...
            g_ts.sizeStart.cx = g_cxMinimized;
            g_ts.sizeStart.cy = g_cySize + 2*g_cyEdge;
        }
        SetWindowPos(hwndStart, NULL, 0, 0, g_ts.sizeStart.cx, g_ts.sizeStart.cy, SWP_NOZORDER | SWP_NOACTIVATE);
    }
}

//----------------------------------------------------------------------------
// Allow us to do stuff on a "button-down".
LRESULT CALLBACK StartButtonSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    // PFileCabinet pfc;
    LRESULT lRet;
    static UINT uDown = 0;

    Assert(g_ButtonProc)


    // Is the button going down?
    if (uMsg == BM_SETSTATE)
    {
        if (wParam) {
            // DebugMsg(DM_TRACE, "c.stswp: Set state %d", wParam);
            // Yep, Is it already down?
            if (!uDown)
            {
                // Nope.
                uDown = 1;
                // Show the button down.
                lRet = CallWindowProc(g_ButtonProc, hWnd, uMsg, wParam, lParam);
                // Notify the parent.
                SendMessage(GetParent(hWnd), WM_COMMAND,
                            (WPARAM)LOWORD(GetDlgCtrlID(hWnd)), (LPARAM)hWnd);
                return lRet;
            }
            else
            {
                // Yep. Do nothing.
                // fDown = FALSE;
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
        }
        else
        {
            // DebugMsg(DM_TRACE, "c.stswp: Set state %d", wParam);
            // Nope, buttons coming up.
            // Is it supposed to be down?
            if (uDown == 1)
            {
                // Yep, do nothing.
                uDown = 2;
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
            else
            {
                // Nope, Forward it on.
                uDown = 0;
                return CallWindowProc(g_ButtonProc, hWnd, uMsg, wParam, lParam);
            }
        }
    }
    else
    {
        if (uMsg == WM_MOUSEMOVE) {
            MSG msg;
            msg.lParam = lParam;
            msg.wParam = wParam;
            msg.message = uMsg;
            msg.hwnd = hWnd;
            SendMessage(g_ts.hwndTrayTips, TTM_RELAYEVENT, 0, (LPARAM)(LPMSG)&msg);
        }
        return CallWindowProc(g_ButtonProc, hWnd, uMsg, wParam, lParam);
    }
}

const TCHAR c_szButton[] = TEXT("button");

/*------------------------------------------------------------------
** create the toolbar with the three buttons and align windows
**------------------------------------------------------------------*/
HWND Tray_MakeStartButton(HWND hwndTray)
{
    HWND hwnd;
    HBITMAP hbm;

    // BUGBUG: BS_CENTER | VS_VCENTER required, user bug?

    hwnd = CreateWindowEx(0, c_szButton, NULL,
        WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS |
        BS_PUSHBUTTON | BS_BITMAP | BS_LEFT | BS_VCENTER,
        0, 0, 0, 0, hwndTray, (HMENU)IDC_START, hinstCabinet, NULL);

    if (hwnd) {
        // Subclass it.
        g_ts.hwndStart = hwnd;
        g_ButtonProc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC);
        SetWindowLong(hwnd, GWL_WNDPROC, (LONG)(FARPROC)StartButtonSubclassWndProc);

        hbm = CreateStartBitmap(hwndTray);
        if (hbm) {
            SendMessage(hwnd, BM_SETIMAGE, IMAGE_BITMAP, (DWORD)hbm);
            AlignStartButton(hwnd);
            return hwnd;
        }
        DestroyWindow(hwnd);
    }

    return NULL;
}

void Tray_GetWindowSizes(PRECT prcClient, PRECT prcView, PRECT prcClock)
{
    int xFSView, yFSView, cxFSView, cyFSView;
    int xClock, yClock, cxClock, cyClock;
    DWORD dwClockMinSize;

    // size to fill either horizontally or vertically
    if ((prcClient->right - g_ts.sizeStart.cx) > (prcClient->bottom - g_ts.sizeStart.cy))
    {
        //
        // Horizontal - Two cases.  One where we have room below the
        // toolbar to display the clock. so display it there, else
        // display it on the right hand side...
        //
        yFSView = 0;
        cyFSView = prcClient->bottom - yFSView;

        dwClockMinSize = SendMessage(g_ts.hwndNotify, WM_CALCMINSIZE,
                g_ts.sizeStart.cx, 0);
        cxClock = LOWORD(dwClockMinSize);
        cyClock = HIWORD(dwClockMinSize);

#ifdef ALLOW_CLOCK_ON_RIGHT
        //
        // we're now disallowing this case because the clock would
        // jump to the right when a notify hits and makes the clock
        // too wide to fit under the clock
        //
        if ((g_ts.sizeStart.cy + cyClock + g_cyTabSpace) <= prcClient->bottom)
        {
            // put it below
            xClock = 0;
            if (cxClock < g_ts.sizeStart.cx)
                cxClock = g_ts.sizeStart.cx;
            yClock = g_ts.sizeStart.cy + g_cyTabSpace;

            xFSView = cxClock + g_cxFrame;
            cxFSView = prcClient->right - xFSView;
        }
        else
        {
#endif
            // Recalc the min size for horizontal arrangement
            dwClockMinSize = SendMessage(g_ts.hwndNotify, WM_CALCMINSIZE,
                0x7fff, 0);
            cxClock = LOWORD(dwClockMinSize);
            cyClock = HIWORD(dwClockMinSize);

            // xClock = prcClient->right - cxClock - g_cxFrame;  // A little gap...
            xClock = prcClient->right - cxClock;
#ifdef VCENTER_CLOCK
            yClock = (prcClient->bottom-cyClock)/2;     // Center it vertically.
#else
            yClock = 0;
#endif

            xFSView = g_ts.sizeStart.cx + g_cxFrame;
            cxFSView = xClock - xFSView - g_cxFrame;
#ifdef ALLOW_CLOCK_ON_RIGHT
        }
#endif
    }
    else
    {
        // Vertical - Again two cases.  One where we have room to the
        // right to display the clock.  Note: we want some gap here between
        // the clock and toolbar.  If it does not fit, then we will center
        // it at the bottom...
        xFSView = 0;
        cxFSView = prcClient->right - xFSView;

        dwClockMinSize = SendMessage(g_ts.hwndNotify, WM_CALCMINSIZE,
                0x7fff, 0);
        cxClock = LOWORD(dwClockMinSize);
        cyClock = HIWORD(dwClockMinSize);

        if ((g_ts.sizeStart.cx + cxClock + 4 * g_cyTabSpace) < prcClient->right)
        {
            int cyMax = g_ts.sizeStart.cy;;

            // Can fit on the same row!
            xClock = prcClient->right - cxClock - (2 * g_cxEdge);  // A little gap...
            yClock = 0;

            if (cyClock > cyMax)
                cyMax = cyClock;

            yFSView = cyMax + g_cyTabSpace;
            cyFSView = prcClient->bottom - yFSView;

        }
        else
        {
            // Nope put at bottom

            // Recalc the min size for vertical arrangement
            dwClockMinSize = SendMessage(g_ts.hwndNotify, WM_CALCMINSIZE,
                cxFSView, 0);
            cxClock = min(LOWORD(dwClockMinSize), prcClient->right);
            cyClock = HIWORD(dwClockMinSize);

            xClock = (prcClient->right - cxClock) / 2;
            yClock = prcClient->bottom - cyClock - g_cyTabSpace;

            yFSView = g_ts.sizeStart.cy + g_cyTabSpace;
            cyFSView = yClock - yFSView - g_cyTabSpace;
        }
    }

    prcView->left = xFSView;
    prcView->top = yFSView;
    prcView->right = xFSView + cxFSView;
    prcView->bottom = yFSView + cyFSView;

    prcClock->left = xClock;
    prcClock->top = yClock;
    prcClock->right = xClock + cxClock;
    prcClock->bottom = yClock + cyClock;
}

void _TrayRestoreWindowPos(HWND hWnd)
{
    WINDOWPLACEMENT wp;

    //first restore the stuck postitions
    _GetSaveStateAndInitRects();

    wp.length = SIZEOF(wp);
    g_ts.uMoveStuckPlace = g_ts.uStuckPlace;
    wp.rcNormalPosition = g_ts.arStuckRects[g_ts.uStuckPlace];
    wp.showCmd = SW_HIDE;

    SendMessage(g_ts.hwndNotify, TNM_HIDECLOCK, 0, g_ts.fHideClock);

    SetWindowPlacement(hWnd, &wp);
}

//----------------------------------------------------------------------------
// Sort of a registry equivalent of the profile API's.
BOOL Reg_GetStruct(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, LPVOID pData, DWORD *pcbData)
{
    HKEY hkeyNew;
    BOOL fRet = FALSE;
    DWORD dwType;

    if (!GetSystemMetrics(SM_CLEANBOOT) && (RegOpenKeyEx(hkey, pszSubKey, 0L, KEY_READ, &hkeyNew) == ERROR_SUCCESS))
    {
        if (RegQueryValueEx(hkeyNew, (LPVOID)pszValue, 0, &dwType, pData, pcbData) == ERROR_SUCCESS)
        {
            fRet = TRUE;
        }
        RegCloseKey(hkeyNew);
    }
    return fRet;
}

//----------------------------------------------------------------------------
// Sort of a registry equivalent of the profile API's.
BOOL Reg_SetStruct(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, LPVOID lpData, DWORD cbData)
{
    HKEY hkeyNew = hkey;
    BOOL fRet = FALSE;

    if (pszSubKey)
    {
        if (RegCreateKey(hkey, pszSubKey, &hkeyNew) != ERROR_SUCCESS)
        {
            return fRet;
        }
    }

    if (RegSetValueEx(hkeyNew, pszValue, 0, REG_BINARY, lpData, cbData) == ERROR_SUCCESS)
    {
        fRet = TRUE;
    }

    if (pszSubKey)
        RegCloseKey(hkeyNew);

    return fRet;
}

/*-------------------------------------------------------------------
** the screen size has changed, so the docked rectangles need to be
** adjusted to the new screen.  since only the bottom and right change,
** only need to increment/decrement certain edges.
**-------------------------------------------------------------------*/
void ResizeStuckRects(RECT *arStuckRects)
{
    int xChange, yChange;

    xChange = tray_cxScreen - arStuckRects[STICK_RIGHT].right;
    yChange = tray_cyScreen - arStuckRects[STICK_BOTTOM].bottom;

    arStuckRects[STICK_TOP].right  = tray_cxScreen;

    arStuckRects[STICK_LEFT].bottom = tray_cyScreen;

    arStuckRects[STICK_BOTTOM].top    += yChange;
    arStuckRects[STICK_BOTTOM].right  = tray_cxScreen;
    arStuckRects[STICK_BOTTOM].bottom = tray_cyScreen;

    arStuckRects[STICK_RIGHT].left   += xChange;
    arStuckRects[STICK_RIGHT].right  = tray_cxScreen;
    arStuckRects[STICK_RIGHT].bottom = tray_cyScreen;
}

//----------------------------------------------------------------------------
STDAPI Tasks_CreateInstance(REFIID riid, void **ppv);

//---------------------------------------------------------------------------
// Create the tray IShellView instance

HWND Tray_CreateShellView(PFileCabinet pfc)
{
    if (FAILED(Tasks_CreateInstance(&IID_IShellView, &pfc->psv)))
        return NULL;        // This is very bad

    if (FAILED(pfc->psv->lpVtbl->CreateViewWindow(pfc->psv, NULL, NULL, &pfc->sb, NULL, &pfc->hwndView)))
    {
        Assert(pfc->hwndView == NULL);  // the failure case shouls set this
        // pfc->hwndView = NULL;
    }

    return pfc->hwndView;
}

// tray IShellBrowser vtbl
IShellBrowserVtbl s_TraySBVtbl =  {
    // *** IUnknown methods ***
    CFileCabinet_QueryInterface,
    CFileCabinet_AddRef,
    CFileCabinet_Release,

    // *** IOleWindow methods ***
    CFileCabinet_GetWindow,
    ShellDesktop_ContextSensitiveHelp,

    // *** IShellBrowser methods ***
    ShellDesktop_InsertMenus,
    ShellDesktop_SetMenu,
    ShellDesktop_RemoveMenus,
    ShellDesktop_SetStatusText,
    CFileCabinet_EnableModeless,
    CFileCabinet_TranslateAccelerator,

    // *** IShellBrowser methods ***
    ShellDesktop_BrowseObject,
    CDeskTray_GetViewStateStream,
    ShellDesktop_GetControlWindow,
    ShellDesktop_SendControlMsg,
    CFileCabinet_QueryActiveShellView,
    ShellDesktop_OnViewWindowActive,
    ShellDesktop_SetToolbarItems,
};


void UpdateDockingFlags()
{
#ifndef WINNT
    BOOL fIoSuccess;
    BIOSPARAMS bp;
    DWORD cbOut;
#endif
    static BOOL fFirstTime=TRUE;

    if ((!fFirstTime) && (g_ts.hBIOS == INVALID_HANDLE_VALUE)) {
        return;
    }
#ifdef WINNT
    g_ts.hBIOS = INVALID_HANDLE_VALUE;
    fFirstTime = FALSE;
    return;

#else

    g_ts.hBIOS = CreateFile(c_szBIOSDevice,
        GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, 0, NULL);

    if (g_ts.hBIOS == INVALID_HANDLE_VALUE) {
        fFirstTime = FALSE;
        return;
    }

    bp.bp_ret=0;

    fIoSuccess=DeviceIoControl(
        g_ts.hBIOS,
        PNPBIOS_SERVICE_GETDOCKCAPABILITIES,
        &bp,
        SIZEOF(bp),
        &bp,
        SIZEOF(bp),
        &cbOut,
        NULL);

    if (fFirstTime) {

        // first time through, if a warm dock change is not possible,
        // don't bother with the menu item.

        fFirstTime=FALSE;

        if (!fIoSuccess) {

            // problem getting the dock capabilities:
            // if problem wasn't "undocked now" or "can't identify dock"
            // then warm ejecting isn't possible.

            if ((bp.bp_ret!=PNPBIOS_ERR_SYSTEM_NOT_DOCKED) &&
                (bp.bp_ret!=PNPBIOS_ERR_CANT_DETERMINE_DOCKING)) {
                CloseHandle(g_ts.hBIOS);
                g_ts.hBIOS=INVALID_HANDLE_VALUE;
                return;
            }
        } else {

            // success getting the dock capabilities:
            // if the dock isn't capable of warm or hot docking
            // then warm ejecting isn't possible

            if (!(bp.bp_ret&PNPBIOS_DOCK_CAPABILITY_TEMPERATURE)) {
                CloseHandle(g_ts.hBIOS);
                g_ts.hBIOS=INVALID_HANDLE_VALUE;
                return;
            }
        }
    }

    // on each call we update WARMEJECTABLENOW
    // depending on whether the dock is capable of warm ejecting right now

    if ((fIoSuccess) && (bp.bp_ret&PNPBIOS_DOCK_CAPABILITY_TEMPERATURE)) {
        g_ts.fDockingFlags|=DOCKFLAG_WARMEJECTABLENOW;
    } else {
        g_ts.fDockingFlags&=~DOCKFLAG_WARMEJECTABLENOW;
    }

    return;
#endif
}

/* Check if system is currently in a docking station.
 *
 * Returns: TRUE if docked, FALSE if undocked or can't tell.
 *
 */

BOOL GetDockedState(void)
{
#ifndef WINNT           // BUGBUG - Fix this when NT gets docking capability
    struct _ghwpi {                 // Get_Hardware_Profile_Info parameter blk
        CMAPI   cmApi;
        ULONG   ulIndex;
        PFARHWPROFILEINFO pHWProfileInfo;
        ULONG   ulFlags;
        HWPROFILEINFO HWProfileInfo;
    } *pghwpi;

    HANDLE hCMHeap;
    UINT Result = DOCKSTATE_UNKNOWN;
    DWORD dwRecipients = BSM_VXDS;

#define HEAP_SHARED     0x04000000      /* put heap in shared memory--undoc'd */

    // Create a shared heap for CONFIGMG parameters

    if ((hCMHeap = HeapCreate(HEAP_SHARED, 1, 4096)) == NULL)
        return DOCKSTATE_UNKNOWN;

    // Allocate parameter block in shared memory

    pghwpi = (struct _ghwpi *)HeapAlloc(hCMHeap, HEAP_ZERO_MEMORY,
                                        SIZEOF(*pghwpi));
    if (pghwpi == NULL)
    {
        HeapDestroy(hCMHeap);
        return DOCKSTATE_UNKNOWN;
    }

    pghwpi->cmApi.dwCMAPIRet     = 0;
    pghwpi->cmApi.dwCMAPIService = GetVxDServiceOrdinal(_CONFIGMG_Get_Hardware_Profile_Info);
    pghwpi->cmApi.pCMAPIStack    = (DWORD)(((LPBYTE)pghwpi) + SIZEOF(pghwpi->cmApi));
    pghwpi->ulIndex              = 0xFFFFFFFF;
    pghwpi->pHWProfileInfo       = &pghwpi->HWProfileInfo;
    pghwpi->ulFlags              = 0;

    // "Call" _CONFIGMG_Get_Hardware_Profile_Info service

    BroadcastSystemMessage(0, &dwRecipients, WM_DEVICECHANGE, DBT_CONFIGMGAPI32,
                           (LPARAM)pghwpi);

    if (pghwpi->cmApi.dwCMAPIRet == CR_SUCCESS) {

        switch (pghwpi->HWProfileInfo.HWPI_dwFlags) {

            case CM_HWPI_DOCKED:
                Result = DOCKSTATE_DOCKED;
                break;

            case CM_HWPI_UNDOCKED:
                Result = DOCKSTATE_UNDOCKED;
                break;

            default:
                Result = DOCKSTATE_UNKNOWN;
                break;

        }

    }

    HeapDestroy(hCMHeap);

    return Result;
#else
    return(FALSE);
#endif
}
#undef HEAP_SHARED

const TCHAR c_szREGSTR_ROOT_APM[] = REGSTR_KEY_ENUM TEXT("\\") REGSTR_KEY_ROOTENUM TEXT("\\") REGSTR_KEY_APM TEXT("\\") REGSTR_DEFAULT_INSTANCE;
const TCHAR c_szREGSTR_BIOS_APM[] = REGSTR_KEY_ENUM TEXT("\\") REGSTR_KEY_BIOSENUM TEXT("\\") REGSTR_KEY_APM;
const TCHAR c_szREGSTR_VAL_APMMENUSUSPEND[] = REGSTR_VAL_APMMENUSUSPEND;

/* Open the registry APM device key
 */

BOOL OpenAPMKey(HKEY *phKey)
{
    HKEY hBiosSys;
    BOOL rc = FALSE;
    TCHAR szInst[MAX_PATH+1];
    DWORD cchInst = ARRAYSIZE(szInst);

    // Open HKLM\Enum\Root\*PNP0C05\0000 - This is the APM key for
    // non-PnP BIOS machines.

    if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szREGSTR_ROOT_APM, phKey) == ERROR_SUCCESS)
        return TRUE;

    // Open HKLM\Enum\BIOS\*PNP0C05, Enum the 1st subkey, open that.  Example:
    // HKLM\Enum\BIOS\*PNP0C05\03.

    if (RegOpenKey(HKEY_LOCAL_MACHINE,c_szREGSTR_BIOS_APM,&hBiosSys) == ERROR_SUCCESS)
    {
        if (RegEnumKey(hBiosSys, 0, szInst, cchInst) == ERROR_SUCCESS &&
            RegOpenKey(hBiosSys, szInst, phKey) == ERROR_SUCCESS)
            rc = TRUE;

        RegCloseKey(hBiosSys);
    }

    return rc;
}


/* Determine if "Suspend" should appear on the Tray menu.
 *
 * Returns: TRUE if Suspend should appear on menu, FALSE if not.
 *          Sets g_ts.fSuspendUndocked if Suspend should be disabled
 *          when system is docked.
 */

BOOL IsSuspendAllowed(void)
{
    HKEY hkey;
    DWORD dwType;
    BOOL fRet = TRUE;
    BYTE bMenuSuspend = APMMENUSUSPEND_ENABLED;
    DWORD cbSize = SIZEOF(bMenuSuspend);

#ifdef WINNT        // BUGBUG - Fix this when NT gets APM.
    return FALSE;
#else
    /* Check the Registry APM key for an APMMenuSuspend value.
     * APMMenuSuspend may have the following values: APMMENUSUSPEND_DISABLED,
     * APMMENUSUSPEND_ENABLED, or APMMENUSUSPEND_UNDOCKED.
     *
     * An APMMenuSuspend value of APMMENUSUSPEND_DISABLED means the
     * tray should never show the Suspend menu item on its menu.
     *
     * APMMENUSUSPEND_ENABLED means the Suspend menu item should be shown
     * if the machine has APM support enabled (VPOWERD is loaded).  This is
     * the default.
     *
     * APMMENUSUSPEND_UNDOCKED means the Suspend menu item should be shown,
     * but only enabled when the machine is not in a docking station.
     *
     */

    if (OpenAPMKey(&hkey))
    {
        if (RegQueryValueEx(hkey, (LPTSTR)c_szREGSTR_VAL_APMMENUSUSPEND, 0,
                            &dwType, &bMenuSuspend, &cbSize) == ERROR_SUCCESS)
        {
            bMenuSuspend &= ~(APMMENUSUSPEND_NOCHANGE);     // don't care about nochange flag
            if (bMenuSuspend == APMMENUSUSPEND_UNDOCKED)
                g_ts.fSuspendUndocked = TRUE;
            else
            {
                g_ts.fSuspendUndocked = FALSE;

                if (bMenuSuspend == APMMENUSUSPEND_DISABLED)
                    fRet = FALSE;
            }
        }
        RegCloseKey(hkey);
    }

    if (fRet) {

        DWORD dwPmLevel, cbOut;
        BOOL fIoSuccess;

        // Disable Suspend menu item if 1) only wanted when undocked and
        // system is currently docked, 2) power mgnt level < advanced

        if (g_ts.fSuspendUndocked && g_ts.DockedState == DOCKSTATE_DOCKED)
            fRet = FALSE;
        else
        {
#ifdef WINNT        // BUGBUG - Fix this when NT does APM
            fRet = FALSE;
#else
            fIoSuccess = DeviceIoControl(g_ts.hVPowerD,
                                         VPOWERD_IOCTL_GET_PM_LEVEL, NULL, 0,
                                         &dwPmLevel, SIZEOF(dwPmLevel), &cbOut,
                                         NULL);

            fRet = (fIoSuccess && (dwPmLevel==PMLEVEL_ADVANCED));
#endif
        }

    }

    return fRet;
#endif
}

void Tray_VerifySize(PFileCabinet pfc, BOOL fWinIni)
{
    RECT rc;
    RECT rcView;

    GetWindowRect(pfc->hwndMain, &rc);
    Tray_HandleSizing(pfc, 0, &rc, g_ts.uStuckPlace);


    //
    // if the old view had a height, and now it won't...
    // push it up to at least one height
    //
    // do this only on win ini if we're on the top or bottom
    if (fWinIni && STUCK_HORIZONTAL(g_ts.uStuckPlace)) {
        // stash the old view size;
        GetClientRect(pfc->hwndView, &rcView);

        if (RECTHEIGHT(rcView) && (RECTHEIGHT(rc) == g_cyTrayBorders)) {
            int cyOneRow = g_cySize + 2 * g_cyEdge;
            if (g_ts.uStuckPlace == STICK_TOP) {
                rc.bottom = rc.top + cyOneRow;
            } else {
                rc.top = rc.bottom - cyOneRow;
            }

            // now snap this size.
            Tray_HandleSizing(pfc, 0, &rc, g_ts.uStuckPlace);
        }
    }

    g_ts.fSelfSizing = TRUE;
    SetWindowPos(pfc->hwndMain, NULL, rc.left, rc.top, RECTWIDTH(rc),RECTHEIGHT(rc), SWP_NOZORDER|SWP_NOACTIVATE);
    StuckSizeChange();
    g_ts.fSelfSizing = FALSE;
}

//----------------------------------------------------------------------------
TCHAR const c_szCheckAssociations[] = TEXT("CheckAssociations");

//----------------------------------------------------------------------------
// Returns true if GrpConv says we should check extensions again (and then
// clears the flag).
// The assumption here is that runonce gets run before we call this (so
// GrpConv -s can set this).
BOOL CheckAssociations(void)
{
    DWORD dw = 0;
    DWORD cb = SIZEOF(dw);

    if (Reg_GetStruct(g_hkeyExplorer, NULL, c_szCheckAssociations,
        &dw, &cb) && dw)
    {
        dw = 0;
        Reg_SetStruct(g_hkeyExplorer, NULL, c_szCheckAssociations, &dw, SIZEOF(dw));
        return TRUE;
    }

    return FALSE;
}

ULONG _RegisterNotify(HWND hwnd, UINT nMsg, LPITEMIDLIST pidl, BOOL fRecursive);

//----------------------------------------------------------------------------
void RegisterDesktopNotify(PFileCabinet pfc)
{
    LPITEMIDLIST pidl;

    DebugMsg(DM_TRACE, TEXT("c.rdn: Notify for desktop."));

    if (!g_ts.uDesktopNotify)
    {
        pidl = SHCloneSpecialIDList(NULL, CSIDL_DESKTOPDIRECTORY, TRUE);
        if (pidl)
        {
            g_ts.uDesktopNotify = _RegisterNotify(pfc->hwndMain, WMTRAY_DESKTOPCHANGE, pidl, FALSE);
            ILFree(pidl);
        }
    }

    if (!SHRestricted(REST_NOCOMMONGROUPS) && !g_ts.uCommonDesktopNotify)
    {
        pidl = SHCloneSpecialIDList(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, TRUE);
        if (pidl)
        {
            g_ts.uCommonDesktopNotify = _RegisterNotify(pfc->hwndMain, WMTRAY_DESKTOPCHANGE, pidl, FALSE);
            ILFree(pidl);
        }
    }
}

LRESULT Tray_OnCreate(HWND hWnd, LPCREATESTRUCT lpcs)
{
    PFileCabinet pfc;
    MINIMIZEDMETRICS mm;

    v_hwndTray = hWnd;

    mm.cbSize = SIZEOF(mm);
    SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, SIZEOF(mm), &mm, FALSE);
    mm.iArrange |= ARW_HIDE;
    SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, SIZEOF(mm), &mm, FALSE);

    UpdateDockingFlags();
    g_ts.DockedState = GetDockedState();

    // Try to open a handle to VPOWERD if the Suspend menu item is allowed.
    // Failure to open VPOWERD will cause the Suspend item to be deleted
    // from the menu later.

#ifndef WINNT           // BUGBUG - Fix this when NT gets APM
    g_ts.hVPowerD = CreateFile(c_szPOWERDevice,
                               GENERIC_READ|GENERIC_WRITE,
                               FILE_SHARE_READ|FILE_SHARE_WRITE,
                               NULL, OPEN_EXISTING, 0, NULL);
#endif

    pfc = CreateSimpleFileCabinet(hWnd, &s_TraySBVtbl);
    if (pfc)
    {
        SetPFC(hWnd, pfc);
        g_pfcTray = pfc;

        pfc->hMainAccel = LoadAccelerators(hinstCabinet, MAKEINTRESOURCE(ACCEL_TRAY));

        pfc->pidl = SHCloneSpecialIDList(NULL, CSIDL_DESKTOPDIRECTORY, FALSE);

        // get a pointer to the tray's information packet
        pfc->hwndToolbar = Tray_MakeStartButton(hWnd);
        if (pfc->hwndToolbar)
        {
            // Clock
            if (CreateClockWindow(hWnd))
            {
                //
                //  We need to set the tray position, before creating
                // the view window, because it will call back our
                // GetWindowRect member functions.
                //
                _TrayRestoreWindowPos(hWnd);

                g_ts.hwndTrayTips = CreateWindow(c_szSToolTipsClass, c_szNULL,
                                                 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
                                                 CW_USEDEFAULT, CW_USEDEFAULT,
                                                 CW_USEDEFAULT, CW_USEDEFAULT,
                                                 NULL, NULL, hinstCabinet,
                                                 NULL);
                SetWindowPos(g_ts.hwndTrayTips, HWND_TOPMOST,
                             0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                if (g_ts.hwndTrayTips) {
                    HWND hwndClock;
                    TOOLINFO ti;

                    ti.cbSize = SIZEOF(ti);
                    ti.uFlags = TTF_IDISHWND;
                    ti.hwnd = hWnd;
                    ti.uId = (UINT)pfc->hwndToolbar;
                    ti.lpszText = (LPTSTR)MAKEINTRESOURCE(IDS_STARTBUTTONTIP);
                    ti.hinst = hinstCabinet;
                    SendMessage(g_ts.hwndTrayTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
                    if (NULL != (hwndClock = (HWND)SendMessage(g_ts.hwndNotify, TNM_GETCLOCK, 0, 0L))) {
                        ti.uFlags = 0;
                        ti.uId = (UINT)hwndClock;
                        ti.lpszText = LPSTR_TEXTCALLBACK;
                        ti.rect.left = ti.rect.top = ti.rect.bottom = ti.rect.right = 0;
                        SendMessage(g_ts.hwndTrayTips, TTM_ADDTOOL, 0, (LPARAM)(LPTOOLINFO)&ti);
                    }
                }

                if (Tray_CreateShellView(pfc))
                {
                    // we need to be unhidden to verify our size
                    TrayUnhide();
                    Tray_VerifySize(pfc, FALSE);
                    Tray_SizeWindows();      // size after all windows created
#ifdef WINNT
                    {
                        // On NT, load different start menu background bitmaps
                        // for workstation vs. server installations

                        NT_PRODUCT_TYPE type;
                        RtlGetNtProductType(&type);

                        if (type == NtProductWinNt)
                        {
                            g_ts.hbmpStartBkg = LoadBitmap(hinstCabinet, MAKEINTRESOURCE(IDB_STARTBKG));
                        }
                        else
                        {
                            g_ts.hbmpStartBkg = LoadBitmap(hinstCabinet, MAKEINTRESOURCE(IDB_SERVERSTARTBKG));
                        }
                    }
#else
                    g_ts.hbmpStartBkg = LoadBitmap(hinstCabinet, MAKEINTRESOURCE(IDB_STARTBKG));
#endif
                    if (g_ts.hbmpStartBkg)
                    {
                        UpdateWindow(hWnd);
                        StartMenu_Build(pfc);
                        CStartDropTarget_Register(pfc);
                        PrintNotify_Init(hWnd);
                        if (CheckAssociations())
                            CheckWinIniForAssocs();
                        RegisterDesktopNotify(pfc);
                        return 1;       // success
                    }
                    else
                    {
                        DebugMsg(DM_ERROR, TEXT("c.t_oc: Unable to load start menu background."));
                    }
                }
            }
        }
    }
    return -1;  // failure
}

BOOL IsHwndObscured(HWND hwnd)
{
    if (IsWindowVisible(hwnd)) {
        RECT rc;
        HWND hwndHit;
        POINT pt;
        GetWindowRect(hwnd, &rc);
        if (rc.left < 0 && 0 < rc.right)
            rc.left = 0;

        if (rc.top < 0 && 0 < rc.bottom)
            rc.top = 0;

        pt.x = rc.left;
        pt.y = rc.top;
        hwndHit = WindowFromPoint(pt);
        if (hwndHit == hwnd ||
            IsChild(hwnd, hwndHit) ||
            IsChild(hwndHit, hwnd)) {

            DebugMsg(DM_TRACE, TEXT("hwnd (%d) is NOT obscured"), hwnd);
            return FALSE;

        }
    }
    DebugMsg(DM_TRACE, TEXT("hwnd (%d) is obscured"), hwnd);
    return TRUE;
}


void DeskTray_DestroyShellView(PFileCabinet pfc)
{
    if (pfc->psv)
    {
        pfc->psv->lpVtbl->DestroyViewWindow(pfc->psv);
        pfc->psv->lpVtbl->Release(pfc->psv);
        pfc->hwndView = NULL;
        pfc->psv = NULL;
    }
}

void Tray_HandleFullScreenApp(BOOL fFullScreen, HWND hwnd)
{
    // in autohide mode, only drop if the window activated has no sysmenu
    if (hwnd) {
        if (fFullScreen && (g_ts.uAutoHide & AH_ON) &&
            GetWindowLong(hwnd, GWL_STYLE) & WS_SYSMENU) {
            //safe to bail here.  fFullScreen is true so we're guaranteed to
            // get another notification later with fFullScreen = FALSE
            fFullScreen = FALSE;
        }
    }

    // only do this if the bit has changed and we're marked for always on top
    if (v_hwndTray) {
        if (g_ts.fAlwaysOnTop &&
            (fFullScreen ? TRUE : FALSE) != (g_ts.fRudeApp ? TRUE : FALSE)) {

            SetWindowPos(v_hwndTray,
                         fFullScreen ? HWND_BOTTOM : HWND_TOPMOST,
                         0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

            AppBarNotifyAll(ABN_FULLSCREENAPP, NULL, fFullScreen);
            g_ts.fRudeApp = fFullScreen ? TRUE : FALSE;

            if (fFullScreen && !IsHwndObscured(v_hwndTray)) {
                // they lied.
                Tray_HandleFullScreenApp(FALSE, hwnd);
                return;
            }
        }

        // Notify clock we are hiding to avoid doing all the paints etc
        if (!(g_ts.uAutoHide & AH_ON)) {
            BOOL fStopClock;

            // before we're willing to stop the clock, make sure we're really obscured
            fStopClock = g_ts.fRudeApp &&
                IsHwndObscured((HWND)SendMessage(g_ts.hwndNotify, TNM_GETCLOCK, 0, 0));

            ClockCtl_HandleTrayHide(fStopClock);
        }
    }
}

/*------------------------------------------------------------------
** Create the tray, toolbar, and ListView windows, get the borders together,
** size to the appropriate shape, and show it.
**------------------------------------------------------------------*/
BOOL InitTray(HINSTANCE hInst)
{
    // initalize globals that need to be non-zero
    g_ts.wThreadCmd = SMCT_DONE;
    g_ts.hBIOS = g_ts.hVPowerD = INVALID_HANDLE_VALUE;

    if (GetSystemMetrics(SM_SLOWMACHINE)) {
        g_dtSlideHide = 0;       // dont slide the tray out
        g_dtSlideShow = 0;
    }
    else {
        //BUGBUG: we should read from registry.
        g_dtSlideHide = 400;
        g_dtSlideShow = 200;
    }

    v_hwndTray = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW,
                                c_szTrayClass, NULL,
                                WS_CLIPCHILDREN | WS_POPUP | WS_BORDER | WS_THICKFRAME,
                                0, 0, 100, 100, NULL, NULL, hInst, NULL);

    if (v_hwndTray) {
        ShowWindow(v_hwndTray, SW_SHOW);

        // no obey the always on top flag
        SetWindowPos(v_hwndTray,
                     g_ts.fAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
                     0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

        UpdateWindow(v_hwndTray);
        StuckSizeChange();

        // this kick starts autohide
        if (g_ts.uAutoHide & AH_ON) {

            // register it
            if (!IAppBarSetAutoHideBar(v_hwndTray, TRUE, g_ts.uStuckPlace)) {

                SetAutoHideState(FALSE);
                ShellMessageBox(hinstCabinet, v_hwndTray, MAKEINTRESOURCE(IDS_ALREADYAUTOHIDEBAR),
                                MAKEINTRESOURCE(IDS_TASKBAR),MB_OK|MB_ICONSTOP);

            } else {
                if (!(g_ts.uAutoHide & AH_HIDING)) {
                    SetAutoHideTimer();
                }
            }
        }

        // this needs to be done here because some of the hotkeys rely on
        // the tray
        RegisterGlobalHotkeys();

        // Init the user defined hotkeys too.
        HotkeyList_Restore(v_hwndTray);

        return TRUE;
    } else
        return FALSE;
}

//----------------------------------------------------------------------------
BOOL WINAPI Reg_GetString(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, LPTSTR psz, DWORD cb)
{
    HKEY hkeyNew;
    BOOL fRet = FALSE;
    DWORD dwType;

    if (!GetSystemMetrics(SM_CLEANBOOT) && (RegOpenKey(hkey, pszSubKey, &hkeyNew) == ERROR_SUCCESS))
    {
        if (RegQueryValueEx(hkeyNew, (LPVOID)pszValue, 0, &dwType, (LPBYTE) psz, &cb) == ERROR_SUCCESS)
        {
            fRet = TRUE;
        }
        RegCloseKey(hkeyNew);
    }
    return fRet;
}

//----------------------------------------------------------------------------
BOOL WINAPI Reg_SetString(HKEY hkey, LPCTSTR pszSubKey, LPCTSTR pszValue, LPCTSTR psz)
{
    HKEY hkeyNew;
    BOOL fRet = FALSE;

    if (RegCreateKey(hkey, pszSubKey, &hkeyNew) == ERROR_SUCCESS)
    {
        if (RegSetValueEx(hkeyNew, pszValue, 0, REG_SZ, (LPBYTE) psz, (lstrlen(psz)+1)*sizeof(TCHAR))  == ERROR_SUCCESS)
        {
            fRet = TRUE;
        }
        RegCloseKey(hkeyNew);
    }
    return fRet;
}

TCHAR c_szRegPathIniExtensions[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Extensions");

//----------------------------------------------------------------------------
// Returns TRUE if there is a proper shell\open\command for the given
// extension that matches the given command line.
// NB This is for MGX Designer which registers an extension and commands for
// printing but relies on win.ini extensions for Open. We need to detect that
// there's no open command and add the appropriate entries to the registry.
// If the given extension maps to a type name we return that in pszTypeName
// otherwise it will be null.
// FMH This also affects Asymetric Compel which makes a new .CPL association.
// We need to merge it up into our Control Panel .CPL association.  We depend
// on Control Panels NOT having a proper Open so users can see both verb sets.
// NB pszLine is the original line from win.ini eg foo.exe /bar ^.fred, see
// comments below...
BOOL Reg_ShellOpenForExtension(LPCTSTR pszExt, LPTSTR pszCmdLine,
    LPTSTR pszTypeName, int cchTypeName, LPCTSTR pszLine)
{
    TCHAR sz[MAX_PATH];
    TCHAR szExt[MAX_PATH];
    LONG cb;

    if (pszTypeName)
        pszTypeName[0] = TEXT('\0');

    // Is the extension registed at all?
    cb = SIZEOF(sz);
    sz[0] = TEXT('\0');
    if (RegQueryValue(HKEY_CLASSES_ROOT, pszExt, sz, &cb) == ERROR_SUCCESS)
    {
        // Is there a file type?
        if (*sz)
        {
            // Yep, check there.
            // DebugMsg(DM_TRACE, "c.r_rofe: Extension has a file type name %s.", sz);
            lstrcpy(szExt, sz);
            if (pszTypeName)
                lstrcpyn(pszTypeName, sz, cchTypeName);
        }
        else
        {
            // No, check old style associations.
            // DebugMsg(DM_TRACE, "c.r_rofe: Extension has no file type name.", pszExt);
            lstrcpy(szExt, pszExt);
        }

        // See if there's an open command.
        lstrcat(szExt, TEXT("\\"));
        lstrcat(szExt, c_szShellOpenCommand);
        cb = SIZEOF(sz);
        if (RegQueryValue(HKEY_CLASSES_ROOT, szExt, sz, &cb) == ERROR_SUCCESS)
        {
            // DebugMsg(DM_TRACE, "c.r_rofe: Extension %s already registed with an open command.", pszExt);
            // NB We want to compare the paths only, not the %1 stuff.
            if (PathIsRelative(pszCmdLine))
            {
                int cch;
                // If a relative path was passed in, we may have a fully qualifed
                // one that is now in the registry... In that case we should
                // say that it matches...
                LPTSTR pszT = PathGetArgs(sz);

                if (pszT)
                {
                    *(pszT-1) = TEXT('\0');
                }

                PathUnquoteSpaces(sz);

                PathRemoveBlanks(pszCmdLine);

                cch = lstrlen(sz) - lstrlen(pszCmdLine);

                if ((cch >= 0) && (lstrcmpi(sz+cch, pszCmdLine) == 0))
                {
                    // DebugMsg(DM_TRACE, "c.r_rofe: Open commands match.");
                    return TRUE;
                }

                lstrcat(pszCmdLine, c_szSpace);    // Append blank back on...
            }
            else
            {
                // If absolute path we can cheat for matches
                *(sz+lstrlen(pszCmdLine)) = TEXT('\0');
                if (lstrcmpi(sz, pszCmdLine) == 0)
                {
                    // DebugMsg(DM_TRACE, "c.r_rofe: Open commands match.");
                    return TRUE;
                }
            }

            // DebugMsg(DM_TRACE, "c.r_rofe: Open commands don't match.");

            // Open commands don't match, check to see if it's because the ini
            // changed (return FALSE so the change is reflected in the registry) or
            // if the registry changed (return TRUE so we keep the registry the way
            // it is.
            if (Reg_GetString(HKEY_LOCAL_MACHINE, c_szRegPathIniExtensions, pszExt, sz, SIZEOF(sz)))
            {
                if (lstrcmpi(sz, pszLine) == 0)
                    return TRUE;
            }

            return FALSE;
        }
        else
        {
            // DebugMsg(DM_TRACE, "c.r_rofe: Extension %s already registed but with no open command.", pszExt);
            return FALSE;
        }
    }

    // DebugMsg(DM_TRACE, "c.r_rofe: No open command for %s.", pszExt);

    return FALSE;
}

//----------------------------------------------------------------------------
const TCHAR c_szDotDGW[] = TEXT(".dgw");
const TCHAR c_szDesignWDotExeSpace[] = TEXT("designw.exe ");
const TCHAR c_szHDesign[] = TEXT("HDesign");

//----------------------------------------------------------------------------
BOOL _PathIsExe(LPCTSTR pszPath)
{
    TCHAR szPath[MAX_PATH];

    lstrcpy(szPath, pszPath);
    PathRemoveBlanks(szPath);

    return PathIsExe(szPath);
}

//----------------------------------------------------------------------------
// Return TRUE for exe, com, bat, pif and lnk.
BOOL ReservedExtension(LPCTSTR pszExt)
{
    TCHAR szExt[5];  // Dot+ext+null.

    lstrcpyn(szExt, pszExt, ARRAYSIZE(szExt));
    PathRemoveBlanks(szExt);
    if (PathIsExe(szExt) || (lstrcmpi(szExt, c_szDotLnk) == 0))
    {
        return TRUE;
    }

    return FALSE;
}

//----------------------------------------------------------------------------
// This function will read in the extensions section of win.ini to see if
// there are any old style associations that we have not accounted for.
// NB Some apps screw up if their extensions magically disappear from the
// extensions section so DON'T DELETE the old entries from win.ini.
#define CWIFA_SIZE  4096
void CheckWinIniForAssocs(void)
{

    LPTSTR pszBuf;
    int cbRet;
    LPTSTR pszLine;
    TCHAR szExtension[MAX_PATH];
    TCHAR szTypeName[MAX_PATH];
    TCHAR szCmdLine[MAX_PATH];
    LPTSTR pszExt;
    LPTSTR pszT;
    BOOL fAssocsMade = FALSE;

    szExtension[0]=TEXT('.');
    szExtension[1]=TEXT('\0');

// BUGBUG - BobDay - This code doesn't handle larger section
    pszBuf = (LPTSTR)LocalAlloc(LPTR, CWIFA_SIZE*SIZEOF(TCHAR));
    if (!pszBuf)
        return; // Could not allocate the memory
    cbRet = (int)GetProfileSection(c_szExtensions, pszBuf, CWIFA_SIZE);

    //
    // We now walk through the list to find any items that is not
    // in the registry.
    //
    for (pszLine = pszBuf; *pszLine; pszLine += lstrlen(pszLine)+1)
    {
        // Get the extension for this file into a buffer.
        pszExt = StrChr(pszLine, TEXT('='));
        if (pszExt == NULL)
            continue;   // skip this line

        szExtension[0]=TEXT('.');
        // lstrcpyn will put the null terminator for us.
        // We should now have something like .xls in szExtension.
        lstrcpyn(szExtension+1, pszLine, (int)(pszExt-pszLine)+1);

        // Ignore extensions bigger than dot + 3 chars.
        if (lstrlen(szExtension) > 4)
        {
            DebugMsg(DM_ERROR, TEXT("c.cwia: Invalid extension, skipped."));
            continue;
        }

        pszLine = pszExt+1;     // Points to after the =;
        while (*pszLine == TEXT(' '))
            pszLine++;  // skip blanks

        // Now find the ^ in the command line.
        pszExt = StrChr(pszLine, TEXT('^'));
        if (pszExt == NULL)
            continue;       // dont process

        // Now setup  the command line
        // WARNING: This assumes only 1 ^ and it assumes the extension...
        lstrcpyn(szCmdLine, pszLine, (int)(pszExt-pszLine)+1);

        // Don't bother moving over invalid entries (like the busted .hlp
        // entry VB 3.0 creates).
        if (!_PathIsExe(szCmdLine))
        {
            DebugMsg(DM_ERROR, TEXT("c.cwia: Invalid app, skipped."));
            continue;
        }

        if (ReservedExtension(szExtension))
        {
            DebugMsg(DM_ERROR, TEXT("c.cwia: Invalid extension (%s), skipped."), szExtension);
            continue;
        }

        // Now see if there is already a mapping for this extension.
        if (Reg_ShellOpenForExtension(szExtension, szCmdLine, szTypeName, ARRAYSIZE(szTypeName), pszLine))
        {
            // Yep, Setup the initial list of ini extensions in the registry if they are
            // not there already.
            if (!Reg_GetString(HKEY_LOCAL_MACHINE, c_szRegPathIniExtensions, szExtension, szTypeName, SIZEOF(szTypeName)))
            {
                Reg_SetString(HKEY_LOCAL_MACHINE, c_szRegPathIniExtensions, szExtension, pszLine);
            }
            continue;
        }

        // No mapping.

        // HACK for Expert Home Design. They put an association in win.ini
        // (which we propagate as typeless) but then register a type and a
        // print command the first time they run - stomping on our propagated
        // Open command. The fix is to put their open command under the proper
        // type instead of leaving it typeless.
        if (lstrcmpi(szExtension, c_szDotDGW) == 0)
        {
            if (lstrcmpi(PathFindFileName(szCmdLine), c_szDesignWDotExeSpace) == 0)
            {
                // Put in a ProgID for them.
                RegSetValue(HKEY_CLASSES_ROOT, szExtension, REG_SZ, c_szHDesign, 0L);
                // Force Open command under their ProgID.
                DebugMsg(DM_TRACE, TEXT("c.cwifa: Expert Home Design special case hit."));
                lstrcpy(szTypeName, c_szHDesign);
            }
        }

        //
        // HACK for Windows OrgChart which does not register OLE1 class
        // if ".WOC" is registered in the registry.
        //
        if (lstrcmpi(szExtension, TEXT(".WOC")) == 0)
        {
            if (lstrcmpi(PathFindFileName(szCmdLine), TEXT("WINORG.EXE ")) == 0)
            {
                DebugMsg(DM_ERROR, TEXT("c.cwia: HACK: Found WINORG (%s, %s), skipped."), szExtension, pszLine);
                continue;
            }
        }

        // Record that we're about to move things over in the registry so we won't keep
        // doing it all the time.
        Reg_SetString(HKEY_LOCAL_MACHINE, c_szRegPathIniExtensions, szExtension, pszLine);

        lstrcat(szCmdLine, TEXT("%1"));

        // see if there are anything else to copy out...
        pszExt++;    // get beyond the ^
        pszT = szExtension;
        while (*pszExt && (CharLower((LPTSTR)(DWORD)*pszExt) == CharLower((LPTSTR)(DWORD)*pszT)))
        {
            // Look for the next character...
            pszExt++;
            pszT++;
        }
        if (*pszExt)
            lstrcat(szCmdLine, pszExt); // add the rest onto the command line

        // Now lets make the actual association.
        // We need to add on the right stuff onto the key...
        if (*szTypeName)
            lstrcpy(szExtension, szTypeName);

        lstrcat(szExtension, TEXT("\\"));
        lstrcat(szExtension, c_szShellOpenCommand);
        RegSetValue(HKEY_CLASSES_ROOT, szExtension, REG_SZ, szCmdLine, 0L);
        // DebugMsg(DM_TRACE, "c.cwifa: %s %s", szExtension, szCmdLine);

        fAssocsMade = TRUE;
    }

    // If we made any associations we should let the cabinet know.
    //
    // Now call off to the notify function.
    if (fAssocsMade)
        SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);

    // And cleanup our allocation
    LocalFree((HLOCAL)pszBuf);
}

//---------------------------------------------------------------------------
void _InitAllSubMenuPopups(HMENU hmenu)
{
    HMENU hmenuSub;
    int i;

    // DebugMsg(DM_IANELHK, "c.iasmp: Initing %x.", hmenu);

    if (!hmenu)
        return;

    if (g_ts.fThreadTerminate)
    {
        return;
    }

    FileMenu_InitMenuPopup(hmenu);

    for (i = GetMenuItemCount(hmenu) - 1; i >= 0 ; i--)
    {
        if (g_ts.fThreadTerminate)
        {
            break;
        }
        if (NULL != (hmenuSub = GetSubMenu(hmenu, i)))
        {
            FileMenu_InitMenuPopup(hmenuSub);
            _InitAllSubMenuPopups(hmenuSub);
        }
    }
}

//---------------------------------------------------------------------------
void WINAPI FillFileMenus(int wID, BOOL fRecurse)
{
    HMENU hmenu = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, wID);
    int i;

    if (!hmenu)
        return;

    if (g_ts.fThreadTerminate)
    {
        DebugMsg(DM_IANELHK, TEXT("c.ffm:...Aborted."));
        return;
    }

    FileMenu_InitMenuPopup(hmenu);

    for (i = GetMenuItemCount(hmenu) - 1; i >= 0 ; i--)
    {
        HMENU hmenuSub;

        if (g_ts.fThreadTerminate)
            break;

        if (NULL != (hmenuSub = GetSubMenu(hmenu, i)))
        {
            if (fRecurse)
                _InitAllSubMenuPopups(hmenuSub);
            else
                FileMenu_InitMenuPopup(hmenuSub);
        }
    }
    // DebugMsg(DM_IANELHK, "c.ffm:...Done");
}

//---------------------------------------------------------------------------
ULONG _RegisterNotify(HWND hwnd, UINT nMsg, LPITEMIDLIST pidl, BOOL fRecursive)
{
    SHChangeNotifyEntry fsne;
    ULONG lReturn;

    // DebugMsg(DM_TRACE, "c.rn: Notify for %s.", (LPSTR)lpszPath);

    fsne.fRecursive = fRecursive;
    fsne.pidl = pidl;

    //
    // Don't watch for attribute changes since we just want the
    // name and icon.  For example, if a printer is paused, we don't
    // want to re-enumuerate everything.
    //
    lReturn =  SHChangeNotifyRegister(hwnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel | SHCNRF_InterruptLevel,
        ((SHCNE_DISKEVENTS | SHCNE_UPDATEIMAGE) & ~SHCNE_ATTRIBUTES), nMsg, 1, &fsne);

    return lReturn;
}

//---------------------------------------------------------------------------
ULONG RegisterNotify(HWND hwnd, UINT nMsg, LPITEMIDLIST pidl)
{
    return _RegisterNotify(hwnd, nMsg, pidl, TRUE);
}

//---------------------------------------------------------------------------
void UnregisterNotify(ULONG nNotify)
{
    if (nNotify)
        SHChangeNotifyDeregister(nNotify);
}

//----------------------------------------------------------------------------
#define HKIF_NULL               0
#define HKIF_CACHED             1
#define HKIF_FREEPIDLS          2

typedef struct
{
    LPITEMIDLIST pidlFolder;
    LPITEMIDLIST pidlItem;
    WORD wGHotkey;
    // BOOL fCached;
    WORD wFlags;
} HOTKEYITEM, *PHOTKEYITEM;

const TCHAR c_szSlashCLSID[] = TEXT("\\CLSID");

//
// like OLE GetClassFile(), but it only works on ProgID\CLSID type registration
// not real doc files or pattern matched files
//

HRESULT _CLSIDFromExtension(LPCTSTR pszExt, CLSID *pclsid)
{
    TCHAR szProgID[80];
    ULONG cb = SIZEOF(szProgID);
    if (RegQueryValue(HKEY_CLASSES_ROOT, pszExt, szProgID, &cb) == ERROR_SUCCESS)
    {
        TCHAR szCLSID[80];

        lstrcat(szProgID, c_szSlashCLSID);
        cb = SIZEOF(szCLSID);

        if (RegQueryValue(HKEY_CLASSES_ROOT, szProgID, szCLSID, &cb) == ERROR_SUCCESS)
            return SHCLSIDFromString(szCLSID, pclsid);
    }
    return E_FAIL;
}


//----------------------------------------------------------------------------
// this gets hotkeys for files that support IShellLink, not just .lnk files
//

WORD _GetHotkeyFromPidls(LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem)
{
    WORD wHotkey = 0;
    LPITEMIDLIST pidl = ILCombine(pidlFolder, pidlItem);
    if (pidl)
    {
        TCHAR szPath[MAX_PATH];

        if (SHGetPathFromIDList(pidl, szPath))
        {
            IShellLink *psl;
            CLSID clsid;

            // BUGBUG: we really should call GetClassFile() but this could
            // slow this down a lot... so chicken out and just look in the registry

            if (FAILED(_CLSIDFromExtension(PathFindExtension(szPath), &clsid)))
                clsid = CLSID_ShellLink;        // assume it's a shell link

            if (SUCCEEDED(ICoCreateInstance(&clsid, &IID_IShellLink, &psl)))
            {
                IPersistFile *ppf;

                if (SUCCEEDED(psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, &ppf)))
                {
                    WCHAR wszPath[MAX_PATH];
                    StrToOleStr(wszPath, szPath);
                    ppf->lpVtbl->Load(ppf, wszPath, 0);
                    ppf->lpVtbl->Release(ppf);
                }
                psl->lpVtbl->GetHotkey(psl, &wHotkey);
#ifdef DEBUG
                // if (wHotkey)
                    // DebugMsg(DM_TRACE, "c.x: %s has hotkey %x.", szPath, wHotkey);
#endif
                psl->lpVtbl->Release(psl);
            }
        }
        ILFree(pidl);
    }

    return wHotkey;
}

//----------------------------------------------------------------------------
UINT HotkeyList_GetFreeItemIndex(void)
{
    int i, cItems;
    PHOTKEYITEM phki;

    cItems = DSA_GetItemCount(g_ts.hdsaHKI);
    for (i=0; i<cItems; i++)
    {
        phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
        if (!phki->wGHotkey)
        {
            Assert(!phki->pidlFolder);
            Assert(!phki->pidlItem);
            break;
        }
    }
    return i;
}

//----------------------------------------------------------------------------
// Weird, Global hotkeys use different flags for modifiers than window hotkeys
// (and hotkeys returned by the hotkey control)
WORD MapHotkeyToGlobalHotkey(WORD wHotkey)
{
    UINT nVirtKey;
    UINT nMod = 0;

    // Map the modifiers.
    if (HIBYTE(wHotkey) & HOTKEYF_SHIFT)
        nMod |= MOD_SHIFT;
    if (HIBYTE(wHotkey) & HOTKEYF_CONTROL)
        nMod |= MOD_CONTROL;
    if (HIBYTE(wHotkey) & HOTKEYF_ALT)
        nMod |= MOD_ALT;
    nVirtKey = LOBYTE(wHotkey);
    return (WORD)((nMod*256) + nVirtKey);
}

//----------------------------------------------------------------------------
// NB This takes a regular window hotkey not a global hotkey (it does
// the convertion for you).
int HotkeyList_Add(WORD wHotkey, LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem, BOOL fClone)
{
    LPITEMIDLIST pidl1, pidl2;

    if (wHotkey)
    {
        HOTKEYITEM hki;
        int i = HotkeyList_GetFreeItemIndex();

        Assert(g_ts.hdsaHKI);

        // DebugMsg(DM_IANELHK, "c.hl_a: Hotkey %x with id %d.", wHotkey, i);

        if (fClone)
        {
            pidl1 = ILClone(pidlFolder);
            pidl2 = ILClone(pidlItem);
            hki.wFlags = HKIF_FREEPIDLS;
        }
        else
        {
            pidl1 = pidlFolder;
            pidl2 = pidlItem;
            hki.wFlags = HKIF_NULL;
        }

        hki.pidlFolder = pidl1;
        hki.pidlItem = pidl2;
        hki.wGHotkey = MapHotkeyToGlobalHotkey(wHotkey);
        DSA_SetItem(g_ts.hdsaHKI, i, &hki);
        return i;
    }

    return -1;
}

//----------------------------------------------------------------------------
// NB Cached hotkeys have their own pidls that need to be free but
// regular hotkeys just keep a pointer to pidls used by the startmenu and
// so don't.
int HotkeyList_AddCached(WORD wGHotkey, LPITEMIDLIST pidl)
{
    int i = -1;

    if (wGHotkey)
    {
        LPITEMIDLIST pidlItem = ILClone(ILFindLastID(pidl));

        Assert(g_ts.hdsaHKI);

        if (pidlItem)
        {
            if (ILRemoveLastID(pidl))
            {
                HOTKEYITEM hki;

                i = HotkeyList_GetFreeItemIndex();

                // DebugMsg(DM_IANELHK, "c.hl_ac: Hotkey %x with id %d.", wGHotkey, i);

                hki.pidlFolder = pidl;
                hki.pidlItem = pidlItem;
                hki.wGHotkey = wGHotkey;
                hki.wFlags = HKIF_CACHED | HKIF_FREEPIDLS;
                DSA_SetItem(g_ts.hdsaHKI, i, &hki);
            }
        }
    }

    return i;
}

const TCHAR c_szHotkeys[] = TEXT("Hotkeys");

//----------------------------------------------------------------------------
// NB Must do this before destroying the startmenu since the hotkey list
// uses it's pidls in a lot of cases.
int HotkeyList_Save(void)
{
    int i, cItems;
    PHOTKEYITEM phki;
    LPITEMIDLIST pidl;
    int cCached = 0;
    int cbData = 0;
    TCHAR szValue[32];
    LPBYTE pData;
    Assert(g_ts.hdsaHKI);

    DebugMsg(DM_TRACE, TEXT("c.hl_s: Saving global hotkeys."));

    cItems = DSA_GetItemCount(g_ts.hdsaHKI);
    for (i=0; i<cItems; i++)
    {
        phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
        // Non cached item?
        if (phki->wGHotkey && !(phki->wFlags & HKIF_CACHED))
        {
            // Yep, save it.
            pidl = ILCombine(phki->pidlFolder, phki->pidlItem);
            if (pidl)
            {
                cbData = SIZEOF(WORD) + ILGetSize(pidl);
                pData = LocalAlloc(GPTR, cbData);
                if (pData)
                {
                    // DebugMsg(DM_TRACE, "c.hl_s: Saving %x.", phki->wGHotkey);
                    *((LPWORD)pData) = phki->wGHotkey;
                    memcpy(pData+SIZEOF(WORD), pidl, cbData-SIZEOF(DWORD));
                    wsprintf(szValue, TEXT("%d"), cCached);
                    Reg_SetStruct(g_hkeyExplorer, c_szHotkeys, szValue, pData, cbData);
                    cCached++;
                    LocalFree(pData);
                }
                ILFree(pidl);
            }
        }
    }

    return cCached;
}


//----------------------------------------------------------------------------
HKEY Reg_EnumValueCreate(HKEY hkey, LPCTSTR pszSubkey)
{
    HKEY hkeyOut;

    if (RegOpenKeyEx(hkey, pszSubkey, 0L, KEY_ALL_ACCESS, &hkeyOut) == ERROR_SUCCESS)
    {
        return hkeyOut;
    }
    return(NULL);
}

//----------------------------------------------------------------------------
int Reg_EnumValue(HKEY hkey, int i, LPBYTE pData, int cbData)
{
    TCHAR szValue[MAX_PATH];
    DWORD cchValue;
    DWORD dw;
    DWORD dwType;

    cchValue = ARRAYSIZE(szValue);
    if (RegEnumValue(hkey, i, szValue, &cchValue, &dw, &dwType, pData,
        &cbData) == ERROR_SUCCESS)
    {
        return cbData;
    }

    return 0;
}

//----------------------------------------------------------------------------
BOOL Reg_EnumValueDestroy(HKEY hkey)
{
    return RegCloseKey(hkey) == ERROR_SUCCESS;
}

//----------------------------------------------------------------------------
int HotkeyList_Restore(HWND hwnd)
{
    int i = 0;
    HKEY hkey;
    LPBYTE pData;
    int cbData;
    WORD wGHotkey;
    int id;
    LPITEMIDLIST pidl;

    DebugMsg(DM_TRACE, TEXT("c.hl.r: Restoring global hotkeys..."));

    hkey = Reg_EnumValueCreate(g_hkeyExplorer, c_szHotkeys);
    if (hkey)
    {
        cbData = Reg_EnumValue(hkey, i, NULL, 0);
        while (cbData)
        {
            pData = LocalAlloc(GPTR, cbData);
            if (pData)
            {
                Reg_EnumValue(hkey, i, pData, cbData);
                // Get the hotkey and the pidl components.
                wGHotkey = *((LPWORD)pData);
                pidl = ILClone((LPITEMIDLIST)(pData+SIZEOF(WORD)));
                // DebugMsg(DM_TRACE, "c.hl_r: Restoring %x", wGHotkey);
                id = HotkeyList_AddCached(wGHotkey, pidl);
                if (id != -1)
                    Tray_RegisterHotkey(hwnd, id);
                LocalFree(pData);
            }
            i++;
            cbData = Reg_EnumValue(hkey, i, NULL, 0);
        }
        Reg_EnumValueDestroy(hkey);
        // Nuke the cached stuff.
        SHRegDeleteKey(g_hkeyExplorer, c_szHotkeys);
    }
    return i;
}

//----------------------------------------------------------------------------
// We get called back for each item added to a filemenu.
void CALLBACK FileMenu_AddItemCallback(LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem)
{
    WORD wHotkey = _GetHotkeyFromPidls(pidlFolder, pidlItem);
    if (wHotkey)
    {
        int i = HotkeyList_Add(wHotkey, pidlFolder, pidlItem, TRUE);
        if (i != -1)
        {
            // Register in the context of the tray's thread.
            PostMessage(v_hwndTray, WMTRAY_REGISTERHOTKEY, i, 0);
        }
    }
}

//----------------------------------------------------------------------------
// NB Again, this takes window hotkey not a Global one.
// NB This doesn't delete cached hotkeys.
int HotkeyList_Remove(WORD wHotkey)
{
    int i, cItems;
    PHOTKEYITEM phki;
    WORD wGHotkey;

    // DebugMsg(DM_IANELHK, "c.hl_r: Remove hotkey for %x" , wHotkey);

    // Unmap the modifiers.
    wGHotkey = MapHotkeyToGlobalHotkey(wHotkey);
    cItems = DSA_GetItemCount(g_ts.hdsaHKI);
    for (i=0; i<cItems; i++)
    {
        phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
        if (phki && !(phki->wFlags & HKIF_CACHED) && (phki->wGHotkey == wGHotkey))
        {
            // DebugMsg(DM_IANELHK, "c.hl_r: Invalidating %d", i);
            if (phki->wFlags & HKIF_FREEPIDLS)
            {
                if (phki->pidlFolder)
                    ILFree(phki->pidlFolder);
                if (phki->pidlItem)
                    ILFree(phki->pidlItem);
            }
            phki->wGHotkey = 0;
            phki->pidlFolder = NULL;
            phki->pidlItem = NULL;
            phki->wFlags &= ~HKIF_FREEPIDLS;
            return i;
        }
    }

    return -1;
}

//----------------------------------------------------------------------------
// NB This takes a global hotkey.
int HotkeyList_RemoveCached(WORD wGHotkey)
{
    int i, cItems;
    PHOTKEYITEM phki;

    // DebugMsg(DM_IANELHK, "c.hl_rc: Remove hotkey for %x" , wGHotkey);

    cItems = DSA_GetItemCount(g_ts.hdsaHKI);
    for (i=0; i<cItems; i++)
    {
        phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
        if (phki && (phki->wFlags & HKIF_CACHED) && (phki->wGHotkey == wGHotkey))
        {
            // DebugMsg(DM_IANELHK, "c.hl_r: Invalidating %d", i);
            if (phki->wFlags & HKIF_FREEPIDLS)
            {
                if (phki->pidlFolder)
                    ILFree(phki->pidlFolder);
                if (phki->pidlItem)
                    ILFree(phki->pidlItem);
            }
            phki->pidlFolder = NULL;
            phki->pidlItem = NULL;
            phki->wGHotkey = 0;
            phki->wFlags &= ~(HKIF_CACHED | HKIF_FREEPIDLS);
            return i;
        }
    }

    return -1;
}

//----------------------------------------------------------------------------
// We get called back when a submenu is delete.
void CALLBACK FileMenu_InvalidateCallback(LPITEMIDLIST pidlFolder)
{
    int i, cItems;
    PHOTKEYITEM phki;

    // DebugMsg(DM_IANELHK, "c.gm_ic: Invalidating items for %x", pidlFolder);

    cItems = DSA_GetItemCount(g_ts.hdsaHKI);
    for (i=0; i<cItems; i++)
    {
        phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
        if (phki && (phki->pidlFolder == pidlFolder))
        {
            // DebugMsg(DM_IANELHK, "c.gm_ic: Invalidating %d", i);
            phki->wGHotkey = 0;
            phki->pidlFolder = NULL;
            phki->pidlItem = NULL;
            PostMessage(v_hwndTray, WMTRAY_UNREGISTERHOTKEY, i, 0);
        }
    }
}

//----------------------------------------------------------------------------
void CALLBACK FileMenu_Callback(LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem)
{
    // A null item means that this is a delete of all the filemenu items in the
    // given folder.
    if (pidlItem)
        FileMenu_AddItemCallback(pidlFolder, pidlItem);
    else
        FileMenu_InvalidateCallback(pidlFolder);
}

//----------------------------------------------------------------------------
// NB Some (the ones not marked HKIF_FREEPIDLS) of the items in the list of hotkeys
// have pointers to idlists used by the filemenu so they are only valid for
// the lifetime of the filemenu.
BOOL HotkeyList_Create(void)
{
    if (!g_ts.hdsaHKI)
    {
        // DebugMsg(DM_TRACE, "c.hkl_c: Creating global hotkey list.");
        g_ts.hdsaHKI = DSA_Create(SIZEOF(HOTKEYITEM), 0);
    }

    if (g_ts.hdsaHKI)
        return TRUE;

    return FALSE;
}

//----------------------------------------------------------------------------
// Stick the contents of the "favourite" folder at the top of the start menu.
void StartMenu_InsertFavouriteItems(PFileCabinet pfc, HMENU hmenu)
{
    LPITEMIDLIST pidlFav;
    UINT cItems;
    UINT fFilter = SHCONTF_NONFOLDERS;

    Assert(pfc);

    // create it so that we have soemthingto register notifies on
    pidlFav = SHCloneSpecialIDList(NULL, CSIDL_STARTMENU, TRUE);
    if (pidlFav)
    {

        if (!SHRestricted(REST_NOSTARTMENUSUBFOLDERS))
            fFilter |= SHCONTF_FOLDERS;

        cItems = FileMenu_InsertUsingPidl(hmenu, IDM_STARTMENU, pidlFav,
            FMF_NOEMPTYITEM | FMF_NOPROGRAMS, fFilter, FileMenu_Callback);
        if (!g_ts.uFavNotify)
            g_ts.uFavNotify = RegisterNotify(pfc->hwndMain, WMTRAY_FAVCHANGE, pidlFav);
        // If there are any items, insert a sep.
        if (cItems)
            FileMenu_AppendItem(hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0);
        ILFree(pidlFav);
    }

    if (!SHRestricted(REST_NOCOMMONGROUPS)) {

        // Favorites from the common profile
        // create it so that we have soemthingto register notifies on
        pidlFav = SHCloneSpecialIDList(NULL, CSIDL_COMMON_STARTMENU, TRUE);
        if (pidlFav)
        {

            cItems = FileMenu_AppendFilesForPidl(hmenu, pidlFav, FALSE);

            if (!g_ts.uCommonFavNotify)
                g_ts.uCommonFavNotify = RegisterNotify(pfc->hwndMain, WMTRAY_COMMONFAVCHANGE, pidlFav);

            // If there are any items, insert a sep.
            if (cItems)
                FileMenu_AppendItem(hmenu, (LPTSTR)FMAI_SEPARATOR, 0, -1, NULL, 0);
            ILFree(pidlFav);
        }
    }
}

//----------------------------------------------------------------------------
BOOL EnumFolder_GetHotkeys(LPSHELLFOLDER psf, HWND hwndOwner,
        LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem)
{
    WORD wHotkey;

    DebugMsg(DM_TRACE, TEXT("c.ef_gh: ..."));
    if (g_ts.fThreadTerminate)
        return FALSE;

    wHotkey = _GetHotkeyFromPidls(pidlFolder, pidlItem);
    if (wHotkey)
    {
        int i = HotkeyList_Add(wHotkey, pidlFolder, pidlItem, TRUE);
        if (i != -1)
        {
            // Register in the context of the tray's thread.
            PostMessage(v_hwndTray, WMTRAY_REGISTERHOTKEY, i, 0);
        }
    }

    return TRUE;
}

//----------------------------------------------------------------------------
void GetDesktopHotkeys(void)
{
    LPITEMIDLIST pidlDesktop = NULL;
    LPITEMIDLIST pidlCommonDesktop = NULL;
    int i, cItems;
    PHOTKEYITEM phki;

    DebugMsg(DM_TRACE, TEXT("c.gdh: Global hotkeys from desktop."));

    pidlDesktop = SHCloneSpecialIDList(NULL, CSIDL_DESKTOPDIRECTORY, TRUE);
    pidlCommonDesktop = SHCloneSpecialIDList(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, TRUE);

    if (pidlDesktop && pidlCommonDesktop)
    {
        // Clean up all existing (non-cached) desktop hotkeys.
        cItems = DSA_GetItemCount(g_ts.hdsaHKI);
        for (i=0; i<cItems; i++)
        {
            phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
            if (phki && phki->wGHotkey)
            {
                if (!(phki->wFlags & HKIF_CACHED) && (phki->wFlags & HKIF_FREEPIDLS)
                    && phki->pidlFolder &&
                    (ILIsEqual(phki->pidlFolder, pidlDesktop) ||
                     ILIsEqual(phki->pidlFolder, pidlCommonDesktop)))
                {
                    DebugMsg(DM_IANELHK, TEXT("c.gdh: Invalidating %d"), i);
                    if (phki->pidlFolder)
                        ILFree(phki->pidlFolder);
                    if (phki->pidlItem)
                        ILFree(phki->pidlItem);
                    phki->wGHotkey = 0;
                    phki->pidlFolder = NULL;
                    phki->pidlItem = NULL;
                    PostMessage(v_hwndTray, WMTRAY_UNREGISTERHOTKEY, i, 0);
                }
            }
        }

        // Add them back.
        EnumFolder(NULL, pidlDesktop, SHCONTF_NONFOLDERS, (PFNENUMFOLDERCALLBACK)EnumFolder_GetHotkeys);

        if (!SHRestricted(REST_NOCOMMONGROUPS)) {
            EnumFolder(NULL, pidlCommonDesktop, SHCONTF_NONFOLDERS, (PFNENUMFOLDERCALLBACK)EnumFolder_GetHotkeys);
        }
    }

    if (pidlDesktop)
        ILFree(pidlDesktop);

    if (pidlCommonDesktop)
        ILFree(pidlCommonDesktop);

    DebugMsg(DM_TRACE, TEXT("c.gdh: done."));
}

//----------------------------------------------------------------------------
#define BTF_DONELISTOFPATHS   0x0001

//----------------------------------------------------------------------------
DWORD WINAPI StartMenu_BkgThread(LPVOID lpv)
{
    static UINT btflags = 0;

    // Handle faults a bit better.
    _try
    {

        while (!g_ts.fThreadTerminate)
        {
            switch (g_ts.wThreadCmd)
            {
                case SMCT_PRIMARY:
                case SMCT_WININIASSOCS:
                    break;
                case SMCT_BUILDLISTOFPATHS:
                    if (!(btflags & BTF_DONELISTOFPATHS))
                    {
                        RLBuildListOfPaths();
                        btflags |= BTF_DONELISTOFPATHS;
                    }
                    break;

                case SMCT_INITPROGRAMS:
                    Tray_HandleInitMenuPopup(Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_PROGRAMSINIT));
                    break;
                case SMCT_INITRECENT:
                    Tray_HandleInitMenuPopup(Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_RECENTINIT));
                    break;
                case SMCT_PARTFILLPROGRAMS:
                    FillFileMenus(IDM_PROGRAMS, FALSE);
                    break;
                case SMCT_FILLRECENT:
                    FillFileMenus(IDM_RECENT, FALSE);
                    break;
                case SMCT_FILLPROGRAMS:
                    FillFileMenus(IDM_PROGRAMS, TRUE);
                    break;
                case SMCT_DESKTOPHOTKEYS:
                    GetDesktopHotkeys();
                    break;
                case SMCT_FILLFAVOURITE:
                    _InitAllSubMenuPopups(g_ts.hmenuStart);
                    break;
                case SMCT_DONE:
                    goto Done;

                case SMCT_FILLRECENTONLY:
                    FillFileMenus(IDM_RECENT, TRUE);
                    goto Done;
                case SMCT_FILLPROGRAMSONLY:
                    FillFileMenus(IDM_PROGRAMS, TRUE);
                    goto Done;
                case SMCT_DESKTOPHOTKEYSONLY:
                    GetDesktopHotkeys();
                    goto Done;
                default:
                    DebugMsg(DM_ERROR, TEXT("c.sm_bt: Invalid thread command %d."), g_ts.wThreadCmd);
                    goto Done;
            }
            // If we're interupted we will need to repeat the command last command otherwise
            // move on to the next command.
            // NB Commands up to BUILDLISTOFPATHS aren't interuptable so don't need restarting.
            if (!g_ts.fThreadTerminate || (g_ts.wThreadCmd <= SMCT_BUILDLISTOFPATHS))
                g_ts.wThreadCmd++;
        }
    }
    _except (SetErrorMode(SEM_NOGPFAULTERRORBOX),
        UnhandledExceptionFilter(GetExceptionInformation()))
    {
        // Put up a nice error box.
        ShellMessageBox(hinstCabinet, NULL, MAKEINTRESOURCE(IDS_EXCEPTIONMSG),
            MAKEINTRESOURCE(IDS_CABINET), MB_ICONEXCLAMATION|MB_SETFOREGROUND);
    }

Done:
    if (g_ts.fThreadTerminate)
    {
        DebugMsg(DM_IANELHK, TEXT("c.sm_bt: Thread %d forced to terminate (%d)."), g_ts.hThread, g_ts.wThreadCmd);
    }
    else
    {
        g_ts.wThreadCmd = SMCT_DONE;
        // DebugMsg(DM_IANELHK, "c.sm_bt: Thread %d completed normally.", g_ts.hThread);
    }
    Assert(g_ts.hThread);
    CloseHandle(g_ts.hThread);
    g_ts.hThread = NULL;
    return 0;
}

//----------------------------------------------------------------------------
void Thread_CreateLowPri(void)
{
    DWORD idThread;
    LPFileCabinet pfc;

    Assert(!g_ts.hThread);
    pfc = g_pfcTray;

    if (pfc && (pfc->bMainMenuInit))
    {
        DebugMsg(DM_IANELHK, TEXT("c.t_clp: Can't create thread, StartMenu is up or being modified."));
        return;
    }

    g_ts.hThread = CreateThread(NULL, 0, StartMenu_BkgThread, NULL, 0, &idThread);
    if (g_ts.hThread)
    {
        // DebugMsg(DM_IANELHK, "c.t_clp: Thread %d created.", g_ts.hThread);
        SetThreadPriority(g_ts.hThread, THREAD_PRIORITY_BELOW_NORMAL);
    }
    else
    {
        DebugMsg(DM_ERROR, TEXT("c.t_clp: Failed to create thread."));
    }
}

//----------------------------------------------------------------------------
BOOL WINAPI StartMenu_ControlBkgThread(STARTMENUCONTROLTHREAD smct)
{
    BOOL fRet = FALSE;

    switch (smct)
    {
        case SMCT_STOP:
            DebugMsg(DM_IANELHK, TEXT("c.sm_cbt: Stop thread."));
            if (!g_ts.hThread)
            {
                DebugMsg(DM_IANELHK, TEXT("c.sm_cbt: Thread not running."));
                fRet = TRUE;
            }
            else
            {
                DebugMsg(DM_IANELHK, TEXT("c.sm_cbt: Waiting for %x to complete."), g_ts.hThread);
                // Speed up stopping the filemenu.
                FileMenu_AbortInitMenu();
                g_ts.fThreadTerminate = TRUE;
                if (WaitForSingleObject(g_ts.hThread, 1000) == WAIT_TIMEOUT)
                {
                    DECLAREWAITCURSOR;

                    SetWaitCursor();

                    DebugMsg(DM_ERROR, TEXT("c.: Still waiting for %x."), g_ts.hThread);
                    if (MsgWaitForMultipleObjectsLoop(g_ts.hThread, 10*1000) == WAIT_TIMEOUT)
                    {
                        DebugMsg(DM_ERROR, TEXT("c.sm_cbt: Background thread timed-out."));
                    }
                    else
                    {
                        DebugMsg(DM_ERROR, TEXT("c.sm_cbt: Background thread finally completed."));
                    }
                    ResetWaitCursor();
                }
                else
                {
                    DebugMsg(DM_ERROR, TEXT("c.sm_cbt: Background thread completed."));
                }
                g_ts.fThreadTerminate = FALSE;
                // CloseHandle(g_ts.hThread);
                // Assert that the terminated thread reset g_ts.hThread.
                Assert(!g_ts.hThread);
            }
            fRet = TRUE;
            break;

        case SMCT_RESTART:
            // DebugMsg(DM_IANELHK, "c.cm_st: Restart thread.");
            if (g_ts.wThreadCmd == SMCT_DONE)
            {
                // DebugMsg(DM_IANELHK, "c.cm_st: Nothing to restart.");
                fRet = TRUE;
            }
            else
            {
                DebugMsg(DM_IANELHK, TEXT("c.cm_st: Restarting with %d."), g_ts.wThreadCmd);
                if (!g_ts.hThread)
                    Thread_CreateLowPri();
                else
                    DebugMsg(DM_ERROR, TEXT("c.dm_cbt: Thread already running."));
            }
            break;

        case SMCT_PRIMARY:
        case SMCT_FILLPROGRAMSONLY:
        case SMCT_FILLRECENTONLY:
#ifdef DEBUG
            DebugMsg(DM_IANELHK, TEXT("c.cm_st: Handle %s stuff."),
                 SMCT_FILLRECENTONLY == smct ? TEXT("recent") :
                 SMCT_FILLPROGRAMSONLY == smct ? TEXT("programs") :
                 TEXT("primary")
                 );
#endif
            g_ts.wThreadCmd = (WORD) smct;
            if (!g_ts.hThread)
                Thread_CreateLowPri();
            break;

        case SMCT_DESKTOPHOTKEYSONLY:
            g_ts.wThreadCmd = (WORD) smct;
            if (!g_ts.hThread)
                Thread_CreateLowPri();
            break;
    }
    return fRet;
}

//---------------------------------------------------------------------------
// Add all the items on menuSrc onto the end of menuDst where menu dst is
// a filemenu and menusrc is an ordinary menu. The initial image index to
// use for all non-seperator items is given by iDefImage. If it's -1 then
// no items will get images.
BOOL StartMenu_CatMenu(HMENU hmenuDst, HMENU hmenuSrc, int iDefImage, UINT cyItem)
{
    int i, iMax, iImage;
    TCHAR szText[CCHSZNORMAL];
    HMENU hmenuSubCopy;

    // Recursion would be bad.
    if (hmenuDst == hmenuSrc)
            return FALSE;


    iMax = GetMenuItemCount(hmenuSrc);
    if (iMax != -1)
    {
        MENUITEMINFO mii;
        mii.cbSize = SIZEOF(MENUITEMINFO);

        for (i=0; i<iMax; i++)
        {
            mii.dwTypeData = szText;
            mii.fMask = MIIM_TYPE | MIIM_ID | MIIM_SUBMENU | MIIM_STATE | MIIM_DATA;
            mii.cch = ARRAYSIZE(szText);
            mii.hSubMenu = NULL;
            mii.fType = MFT_SEPARATOR;                // to avoid ramdom result.
            mii.dwItemData = 0;
            GetMenuItemInfo(hmenuSrc, i, TRUE, &mii);

            // Give the settings sub menu's the right icons.
            if ((i == 0) && (iDefImage == -1) && (mii.wID == IDM_CONTROLS))
                iDefImage = II_STCPANEL;

            if (mii.wID == IDM_FILERUN)
                iDefImage = II_STRUN;

            // NB Take out the icons and remove these id's when we're sure
            // the Start Menu is stable.

            // Skip programs(start menu) icon.
            if (iDefImage == II_STSPROGS)
                iDefImage++;

            // Skip fonts icon.
            if (iDefImage == II_STFONTS)
                iDefImage++;

            iImage = iDefImage;

            // Make sure the find menu items have a default image.

            if ((mii.wID >= TRAY_IDM_FINDFIRST) && (mii.wID <= TRAY_IDM_FINDLAST) && !mii.dwItemData)
                    mii.dwItemData = II_STFIND;

            if (mii.fType & MFT_SEPARATOR)
            {
                FileMenu_AppendItem(hmenuDst, (LPTSTR)FMAI_SEPARATOR, mii.wID, -1, NULL, 0);
            }
            else if (mii.hSubMenu)
            {
                // Submenu.
                hmenuSubCopy = FileMenu_Create((COLORREF)-1, 0, NULL, 0, 0);
                // NB Only allow small submenu's for now.
                StartMenu_CatMenu(hmenuSubCopy, mii.hSubMenu, -1, 0);

                FileMenu_AppendItem(hmenuDst, szText, mii.wID, iImage, hmenuSubCopy, cyItem);
                if (iDefImage != -1)
                    iDefImage++;
            }
            else
            {
                if ((iDefImage == -1) && (mii.dwItemData != 0))
                {
                    FileMenu_AppendItem(hmenuDst, szText, mii.wID, mii.dwItemData, NULL, cyItem);
                }
                else
                {
                    FileMenu_AppendItem(hmenuDst, szText, mii.wID, iImage, NULL, cyItem);
                }

                if (iDefImage != -1)
                    iDefImage++;
            }
        }
        return TRUE;
    }
    return FALSE;
}

//----------------------------------------------------------------------------
// Remove restricted items and stuff that is specific to a particular hardware
// platform.
void StartMenu_RemoveUnavailableItems(void)
{
        HMENU hmenu;
        UINT iSep2ItemsMissing = 0;

        // APM/Dockable stuff.
        if (!IsEjectAllowed() || g_ts.DockedState == DOCKSTATE_UNDOCKED)
        {
                FileMenu_DeleteItemByCmd(g_ts.hmenuStart, IDM_EJECTPC);
                iSep2ItemsMissing++;
        }

        if (g_ts.hVPowerD == INVALID_HANDLE_VALUE || !IsSuspendAllowed())
        {
                FileMenu_DeleteItemByCmd(g_ts.hmenuStart, IDM_SUSPEND);
                iSep2ItemsMissing++;
        }

        // Restictions
        if (SHRestricted(REST_NORUN))
        {
                FileMenu_DeleteItemByCmd(g_ts.hmenuStart, IDM_FILERUN);
        }
        if (SHRestricted(REST_NOCLOSE))
        {
                FileMenu_DeleteItemByCmd(g_ts.hmenuStart, IDM_EXITWIN);
                iSep2ItemsMissing++;
        }

        if (iSep2ItemsMissing == 3)
        {
                FileMenu_DeleteItemByCmd(g_ts.hmenuStart, IDM_SEP2);
        }

        // Setting stuff.
        if (SHRestricted(REST_NOSETTASKBAR) || SHRestricted(REST_NOSETFOLDERS))
        {
                hmenu = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_CONTROLS);
                if (hmenu)
                {
                        // Both?
                        if (SHRestricted(REST_NOSETTASKBAR) && SHRestricted(REST_NOSETFOLDERS))
                        {
                                // Yep, toast the whole settings menu.
                                FileMenu_DeleteMenuItemByFirstID(g_ts.hmenuStart, IDM_CONTROLS);
                        }
                        else
                        {
                                // Nope, just one.
                                if (SHRestricted(REST_NOSETFOLDERS))
                                {
                                        FileMenu_DeleteItemByCmd(hmenu, IDM_CONTROLS);
                                        // FileMenu_DeleteItemByCmd(hmenu, IDM_PROGRAMSFOLDER);
                                        FileMenu_DeleteItemByCmd(hmenu, IDM_PRINTERS);
                                        // FileMenu_DeleteItemByCmd(hmenu, IDM_FONTS);
                                }
                                else if (SHRestricted(REST_NOSETTASKBAR))
                                {
                                        FileMenu_DeleteItemByCmd(hmenu, IDM_TRAYPROPERTIES);
                                }
                                // Delete the seperator.
                                FileMenu_DeleteSeparator(hmenu);
                        }
                }
                else
                {
                        DebugMsg(DM_ERROR, TEXT("c.fm_rui: Settings menu couldn't be found. Restricted items could not be removed."));
                }
        }
        // Find menu.
        if (SHRestricted(REST_NOFIND))
        {
                FileMenu_DeleteItemByCmd(g_ts.hmenuStart, IDM_MENU_FIND);
        }

}

//----------------------------------------------------------------------------
// Make sure the menu fits on the screen. Remove items from the top if it
// doesn't.
void StartMenu_LimitHeight(void)
{
    int i, j, cItems;
    int cyMenu, cyMenuMax;

    Assert(g_ts.hmenuStart);

    cItems = GetMenuItemCount(g_ts.hmenuStart);
    Assert(cItems);
    cyMenu = 0;
    cyMenuMax = GetSystemMetrics(SM_CYSCREEN);
    // Stop when the menu gets to big.
    for (i=cItems; i>0; i--)
    {
        cyMenu += HIWORD(FileMenu_GetItemExtent(g_ts.hmenuStart, i-1));
        if (cyMenu > cyMenuMax)
            break;
    }
    // Delete overflow items.
    for (j=i; j>0; j--)
    {
        FileMenu_DeleteItemByIndex(g_ts.hmenuStart, j-1);
    }
}

//----------------------------------------------------------------------------
void StartMenu_Build(PFileCabinet pfc)
{
    HMENU hMenu, hMenuSub;
    // DWORD idThread;

    // Keep these menu's around - it's no longer safe to just delete
    // them willy-nilly.

    Assert(!g_ts.hmenuStart);
    Assert(!g_ts.hThread);

    hMenu = LoadMenu(hinstCabinet, MAKEINTRESOURCE(MENU_START));
    if (hMenu)
    {
        hMenuSub = GetSubMenu(hMenu, 0);
        if (hMenuSub)
        {
            // initialize the find extensions
            // check to see if it's the find menu
            MENUITEMINFO mii;
            mii.cbSize = SIZEOF(MENUITEMINFO);
            mii.fMask = MIIM_SUBMENU|MIIM_ID;
            if (GetMenuItemInfo(hMenuSub, IDM_MENU_FIND, FALSE, &mii))
            {
                DebugMsg(DM_TRACE, TEXT("InitMenuPopup of Find commands"));
                pfc->pcmFind = SHFind_InitMenuPopup(mii.hSubMenu, pfc->hwndMain, TRAY_IDM_FINDFIRST, TRAY_IDM_FINDLAST);
            } else {
                Assert(0);
            }

            if (g_ts.fSMSmallIcons)
                g_ts.hmenuStart = FileMenu_Create((COLORREF)-1, 0, NULL, 0, FMF_NOBREAK);
            else
                g_ts.hmenuStart = FileMenu_Create(RGB(0,0,0), 21, g_ts.hbmpStartBkg, 0, FMF_LARGEICONS|FMF_NOBREAK);

            if (g_ts.hmenuStart)
            {
                // List for global hotkeys.
                HotkeyList_Create();
                // Startmenu items.
                StartMenu_InsertFavouriteItems(pfc, g_ts.hmenuStart);
                // Default stuff.
                StartMenu_CatMenu(g_ts.hmenuStart, hMenuSub, II_STPROGS, g_ts.fSMSmallIcons ? 0 : g_cyIcon);
                // Remove stuff.
                StartMenu_RemoveUnavailableItems();
                // Limit it's height.
                StartMenu_LimitHeight();
                // Build programs menu's in the background.
                StartMenu_ControlBkgThread(SMCT_PRIMARY);
            }
        }
        DestroyMenu(hMenu);
    }
}

//----------------------------------------------------------------------------
void StartMenu_Destroy(LPFileCabinet pfc)
{
    StartMenu_ControlBkgThread(SMCT_STOP);
    FileMenu_Destroy(g_ts.hmenuStart);
    if (pfc && pfc->pcmFind)
    {
        pfc->pcmFind->lpVtbl->Release(pfc->pcmFind);
        pfc->pcmFind = NULL;
    }
    g_ts.hmenuStart = NULL;
}

//----------------------------------------------------------------------------
void _ForceStartButtonUp()
{
    MSG msg;
    // don't do that check message pos because it gets screwy with
    // keyboard cancel.  and besides, we always want it cleared after
    // track menu popup is done.
    // do it twice to be sure it's up due to the uDown cycling twice in
    // the subclassing stuff
    // pull off any button downs
    PeekMessage(&msg, g_ts.hwndStart, WM_LBUTTONDOWN, WM_LBUTTONDOWN, PM_REMOVE);
    SendMessage(g_ts.hwndStart, BM_SETSTATE, FALSE, 0);
    SendMessage(g_ts.hwndStart, BM_SETSTATE, FALSE, 0);
}


int Tray_TrackMenu(PFileCabinet pfc, HMENU hmenu, BOOL fFileMenu)
{
    TPMPARAMS tpm;
    int iret;

    tpm.cbSize = SIZEOF(tpm);
    GetClientRect(pfc->hwndToolbar, &tpm.rcExclude);
    MapWindowPoints(pfc->hwndToolbar, NULL, (LPPOINT)&tpm.rcExclude, 2);

    SendMessage(g_ts.hwndTrayTips, TTM_ACTIVATE, FALSE, 0L);
    if (fFileMenu)
    {
        iret = FileMenu_TrackPopupMenuEx(hmenu, TPM_VERTICAL | TPM_BOTTOMALIGN | TPM_RETURNCMD,
            tpm.rcExclude.left, tpm.rcExclude.bottom, v_hwndTray, &tpm);
    }
    else
    {
        iret = TrackPopupMenuEx(hmenu, TPM_VERTICAL | TPM_BOTTOMALIGN | TPM_RETURNCMD,
            tpm.rcExclude.left, tpm.rcExclude.bottom, v_hwndTray, &tpm);
    }
    SendMessage(g_ts.hwndTrayTips, TTM_ACTIVATE, TRUE, 0L);
    return iret;
}

//----------------------------------------------------------------------------
// Keep track of the menu font name and weight so we can redo the startmenu if
// these change before getting notified via win.ini.
BOOL StartMenu_FontChange(void)
{
    NONCLIENTMETRICS ncm;
    static TCHAR lfFaceName[LF_FACESIZE] = TEXT("");
    static long lfHeight = 0;

    ncm.cbSize = SIZEOF(ncm);
    if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, SIZEOF(ncm), &ncm, FALSE))
    {
        if (!*lfFaceName)
        {
            // DebugMsg(DM_TRACE, "sm_fc: No menu font - initing.");
            lstrcpy(lfFaceName, ncm.lfMenuFont.lfFaceName);
            lfHeight = ncm.lfMenuFont.lfHeight;
            return FALSE;
        }
        else if ((lstrcmpi(ncm.lfMenuFont.lfFaceName, lfFaceName) == 0) &&
            (ncm.lfMenuFont.lfHeight == lfHeight))
        {
            // DebugMsg(DM_TRACE, "sm_fc: Menu font unchanged.");
            return FALSE;
        }
        else
        {
            // DebugMsg(DM_TRACE, "sm_fc: Menu font changed.");
            lstrcpy(lfFaceName, ncm.lfMenuFont.lfFaceName);
            lfHeight = ncm.lfMenuFont.lfHeight;
            return TRUE;
        }
    }
    return FALSE;
}

/*------------------------------------------------------------------
** Respond to a button's pressing by bringing up the appropriate menu.
** Clean up the button depression when the menu is dismissed.
**------------------------------------------------------------------*/
void ToolbarMenu(PFileCabinet pfc)
{
    HMENU hmenuMain = NULL;
    HMENU hMenu = NULL;
    int idCmd;
    // DWORD idThread;

    SetActiveWindow(v_hwndTray);

#ifndef WINNT       // BUGBUG - Fix this when NT gets docking ability
    if (g_ts.hBIOS!=INVALID_HANDLE_VALUE)
    {
        FileMenu_EnableItemByCmd(g_ts.hmenuStart, IDM_EJECTPC,
                g_ts.fDockingFlags&DOCKFLAG_WARMEJECTABLENOW);
    }
#endif

    // Keep the button down.
    StartMenu_ControlBkgThread(SMCT_STOP);
    if (g_ts.fFavInvalid || StartMenu_FontChange())
        RebuildEntireMenu(pfc);

    pfc->bMainMenuInit = TRUE;
    idCmd = Tray_TrackMenu(pfc, g_ts.hmenuStart, TRUE);
    pfc->bMainMenuInit = FALSE;
    // This forces the button back up.
    _ForceStartButtonUp();
    // Restart the menu thread if needed.
    StartMenu_ControlBkgThread(SMCT_RESTART);

    if (idCmd) {
        TrayCommand(pfc, idCmd);        // execute if a menu was selected
    }
}

void AppBarSubtractRect(PAPPBAR pab, LPRECT lprc)
{

    switch (pab->uEdge) {
        case ABE_TOP:
            if (pab->rc.bottom > lprc->top)
                lprc->top = pab->rc.bottom;
            break;

        case ABE_LEFT:
            if (pab->rc.right > lprc->left)
                lprc->left = pab->rc.right;
            break;

        case ABE_BOTTOM:
            if (pab->rc.top < lprc->bottom)
                lprc->bottom = pab->rc.top;
            break;

        case ABE_RIGHT:
            if (pab->rc.left < lprc->right)
                lprc->right = pab->rc.left;
            break;
    }
}

void AppBarSubtractRects(LPRECT lprc)
{
    int i;

    if (g_ts.hdpaAppBars) {
        i = DPA_GetPtrCount(g_ts.hdpaAppBars);
        // can't use subtract rect because of fully inclusion limitation
        while (i--) {
            PAPPBAR pab = (PAPPBAR)DPA_GetPtr(g_ts.hdpaAppBars, i);
            AppBarSubtractRect(pab, lprc);
        }
    }
}

void GetWorkArea(LPRECT lprcWAreaNew, LPRECT lprcFull, LPRECT lprcTray)
{
    RECT rcWork;

    // Calc size for new work areas. Only limit the screen if the tray is
    // always on top and autohide is off.
    if (!(g_ts.uAutoHide & AH_ON) && g_ts.fAlwaysOnTop)
    {
        SubtractRect(&rcWork, lprcFull, lprcTray);
    } else {
        rcWork = *lprcFull;
    }

    AppBarSubtractRects(&rcWork);
    *lprcWAreaNew = rcWork;
}

/*------------------------------------------------------------------
** tell USER to limit the usable screen size because the tray is
** stuck to one of the sides.
**------------------------------------------------------------------*/
void LimitScreenSize(PRECT prTray)
{
    RECT rcFull;
    RECT rcWork;
    RECT rcWAreaOld;

    rcFull.top = rcFull.left = 0;
    rcFull.right = g_cxScreen;
    rcFull.bottom = g_cyScreen;

    GetWorkArea(&rcWork, &rcFull, prTray);

    // Did anything change?
    SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWAreaOld, 0);
    if (!EqualRect(&rcWork, &rcWAreaOld))
    {
        DebugMsg(DM_TRACE, TEXT("c.lss: Changing desktop work area."));
        SystemParametersInfo(SPI_SETWORKAREA, TRUE, &rcWork, SPIF_SENDWININICHANGE);
    }
}

/*------------------------------------------------------------------
** the tray has either just been stuck to an edge or has resized while
** in the stuck position.  so adjust the world to this real estate
** guzzling action.  Other windows in the system are moved out of the way.
**------------------------------------------------------------------*/
void StuckSizeChange()
{
    GetWindowRect(v_hwndTray, &g_ts.arStuckRects[g_ts.uStuckPlace]);
    LimitScreenSize(&g_ts.arStuckRects[g_ts.uStuckPlace]);
    SendMessage(v_hwndDesktop, DTM_SIZEDESKTOP, 0, 0);
}

/*------------------------------------------------------------------
** the position is changing in response to a move operation.
**
** if the docking status changed, we need to get a new size and
** maybe a new frame style.  change the WINDOWPOS to reflect
** these changes accordingly.
**------------------------------------------------------------------*/
void Tray_DoneMoving(LPWINDOWPOS lpwp)
{
    RECT rc;

    rc = g_ts.arStuckRects[g_ts.uMoveStuckPlace];
    lpwp->x = rc.left;
    lpwp->y = rc.top;
    lpwp->cx = rc.right - rc.left;
    lpwp->cy = rc.bottom - rc.top;

    // the size has changed
    lpwp->flags &= ~SWP_NOSIZE;

    // if we were autohiding, we need to update our appbar autohide rect
    if (g_ts.uAutoHide & AH_ON) {

        // unregister us from the old side
        IAppBarSetAutoHideBar(v_hwndTray, FALSE, g_ts.uStuckPlace);
    }

    // remember the new state
    g_ts.uStuckPlace = g_ts.uMoveStuckPlace;
    if (g_ts.fSysSizing)
        g_ts.rc = g_ts.arStuckRects[g_ts.uStuckPlace];


    if (g_ts.uAutoHide & AH_ON) {
        if (!IAppBarSetAutoHideBar(v_hwndTray, TRUE, g_ts.uStuckPlace)) {

            SetAutoHideState(FALSE);
            ShellMessageBox(hinstCabinet, v_hwndTray, MAKEINTRESOURCE(IDS_ALREADYAUTOHIDEBAR),
                            MAKEINTRESOURCE(IDS_TASKBAR),MB_OK|MB_ICONSTOP);

        }
    }
}

void Tray_WhichStuckRect(int left, int top, int right, int bottom)
{
    int i;
    for (i = STICK_BOTTOM ; i >= STICK_LEFT ; i--) {
        if (g_ts.arStuckRects[i].left == left &&
            g_ts.arStuckRects[i].top == top &&
            g_ts.arStuckRects[i].right == right &&
            g_ts.arStuckRects[i].bottom == bottom) {
            g_ts.uMoveStuckPlace = i;
        }
    }
}

void Tray_CalcStuckPlace(POINT pt)
{
    int i;
    int dx, dy;
    int horiz, vert;

    // BUGBUG go in backward order to encourage good positioning
    // BUGBUG ideally, we want to have the order: b,t,l,r.
    for(i = STICK_BOTTOM; i >= STICK_LEFT; i--)
    {
        if (PtInRect(&(g_ts.arStuckRects[i]), pt))
        {
            g_ts.uMoveStuckPlace = i;
            break;
        }
    }


    // not in the rect, see which side we're closest to.
    if (pt.x < (g_cxScreen/2)) {
        dx = pt.x;
        horiz = STICK_LEFT;
    } else {
        dx = g_cxScreen - pt.x;
        horiz = STICK_RIGHT;
    }

    if (pt.y < (g_cyScreen/2)) {
        dy = pt.y;
        vert = STICK_TOP;
    }  else {
        dy = g_cyScreen - pt.y;
        vert = STICK_BOTTOM;
    }

    // if ((dx / g_cxScreen) < (dy / g_cyScreen))
    // same as above
    if ((g_cxScreen * dy ) > (g_cyScreen * dx))
        g_ts.uMoveStuckPlace = horiz;
    else
        g_ts.uMoveStuckPlace = vert;
}

/*------------------------------------------------------------------
** processing of the WM_MOVING message.
**
** if the cursor is in one of the docking positions, make the
** fuzzy rect look like the docked position.  if not, the fuzzy
** rectangle is the size of the undocked tray.
**
** g_ts.uMoveStuckPlace is set here for general use.
** return: fill in rectangle with new size.
**------------------------------------------------------------------*/
void Tray_HandleMoving(LPRECT lprc)
{
    POINT ptCursor;

    GetCursorPos(&ptCursor);
    Tray_CalcStuckPlace(ptCursor);
    *lprc = g_ts.arStuckRects[g_ts.uMoveStuckPlace];
}

//------------------------------------------------------------------
// Size the icon area to fill as much of the tray window as it can.
//------------------------------------------------------------------
void Tray_SizeWindows()
{
    RECT rcView, rcClock, rcClient;
    PFileCabinet pfc = g_pfcTray;

    if (!g_pfcTray || !pfc->hwndView || !pfc->hwndMain || !g_ts.hwndNotify)
        return;

    if (g_ts.uAutoHide & AH_HIDING) {
        g_ts.fShouldResize = TRUE;
        return;
    }

    GetClientRect(pfc->hwndMain, &rcClient);
    Tray_GetWindowSizes(&rcClient, &rcView, &rcClock);

    SetWindowPos(g_ts.hwndStart, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);

    SetWindowPos(pfc->hwndToolbar, NULL, 0, 0,
                 (rcClient.right < g_ts.sizeStart.cx) ? rcClient.right : g_ts.sizeStart.cx,
                 g_ts.sizeStart.cy, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);

    // position the view
    SetWindowPos(pfc->hwndView, NULL, rcView.left, rcView.top,
                 RECTWIDTH(rcView), RECTHEIGHT(rcView),
                 SWP_NOZORDER | SWP_NOACTIVATE);

    // And the clock
    SetWindowPos(g_ts.hwndNotify, NULL, rcClock.left, rcClock.top,
                 RECTWIDTH(rcClock), RECTHEIGHT(rcClock),
                 SWP_NOZORDER | SWP_NOACTIVATE);

    {
        TOOLINFO ti;
        HWND hwndClock = (HWND)SendMessage(g_ts.hwndNotify, TNM_GETCLOCK, 0, 0L);

        ti.cbSize = SIZEOF(ti);
        ti.uFlags = 0;
        ti.hwnd = pfc->hwndMain;
        ti.lpszText = LPSTR_TEXTCALLBACK;
        ti.uId = (UINT)hwndClock;
        GetWindowRect(hwndClock, &ti.rect);
        MapWindowPoints(HWND_DESKTOP, pfc->hwndMain, (LPPOINT)&ti.rect, 2);
        SendMessage(g_ts.hwndTrayTips, TTM_NEWTOOLRECT, 0, (LPARAM)((LPTOOLINFO)&ti));
    }
    //InvalidateRect(pfc->hwndView, NULL, TRUE);
}



void Tray_NegotiateSizing(PFileCabinet pfc, UINT code, PRECT prcClient)
{
    RECT rcView, rcClock;
    int iHeight;

    // given this sizing, what would our children's sizes be?
    // use Client coords
    prcClient->bottom -= prcClient->top;
    prcClient->right -= prcClient->left;
    prcClient->top = prcClient->left = 0;
    Tray_GetWindowSizes(prcClient, &rcView, &rcClock);
    iHeight = RECTHEIGHT(rcView);

    // maybe we should translate this  to screen coordinates?
    // that would allow our children to prevent being in certain
    // physical locations, but we're more concerned with size, not loc
    SendMessage(pfc->hwndView, WM_SIZING, code, (LPARAM)(LPRECT)&rcView);

    // did it change?
    if (RECTHEIGHT(rcView) != iHeight) {

        // grow the prcClient by the same amound rcView grew;
        prcClient->bottom += RECTHEIGHT(rcView) - iHeight;

        // try again until it doesn't change
        Tray_NegotiateSizing(pfc, code, prcClient);
    }
}


int Window_GetClientGapHeight(HWND hwnd)
{
        RECT rc;

        SetRectEmpty(&rc);
        AdjustWindowRectEx(&rc, GetWindowLong(hwnd, GWL_STYLE), FALSE, GetWindowLong(hwnd, GWL_EXSTYLE));
        return RECTHEIGHT(rc);
}

/*-------------------------------------------------------------
** process the WM_SIZING message.
**
** nothing exciting.  just don't let a docked tray get sized
** too big.
**-------------------------------------------------------------*/
void Tray_HandleSizing(PFileCabinet pfc, UINT code, LPRECT lprc, UINT uStuckPlace)
{
    RECT rc;
    int iHeight;
    int iYExtra; // the amount of Y difference between the window and client height;

    iYExtra = Window_GetClientGapHeight(pfc->hwndMain);
    if (g_cyTrayBorders == -1)
        g_cyTrayBorders = iYExtra; // on initial boot

    // first do original size check.. don't let it be more than half the
    // screen if it's docked.
    switch (uStuckPlace)
    {
        case STICK_TOP:
            lprc->top = tray_yScreen;
            lprc->right = tray_cxScreen;
            lprc->left = tray_xScreen;
            if (lprc->bottom > g_cyScreen/2)
                lprc->bottom = g_cyScreen/2;

            if (RECTHEIGHT(*lprc) <= (g_cyTrayBorders)) {
                // the minmaxsize will prevent us from getting too small
                // allow
                //lprc->bottom = lprc->top + g_cyFrame;
                g_cyTrayBorders = iYExtra;
                return;
            }
            break;
        case STICK_BOTTOM:
            lprc->right = tray_cxScreen;
            lprc->left = tray_xScreen;
            if (lprc->top > tray_cyScreen) {
                lprc->top = tray_cyScreen - (lprc->bottom - lprc->top);
            }
            lprc->bottom = tray_cyScreen;
            if (lprc->top < g_cyScreen/2)
                lprc->top = g_cyScreen/2;

            if (RECTHEIGHT(*lprc) <= (g_cyTrayBorders)) {
                // the minmaxsize will prevent us from getting too small
                // allow
                //lprc->top = lprc->bottom - g_cyFrame;
                g_cyTrayBorders = iYExtra;
                return;
            }
            break;

            // in these two cases, we return, not break because we don't want
            // to arbitrate size if we're stuck to the right or left sides
        case STICK_RIGHT:
            lprc->top = tray_yScreen;
            lprc->bottom = tray_cyScreen;
            if (lprc->left > tray_cxScreen) {
                lprc->top = tray_cxScreen - (lprc->right - lprc->left);
            }
            lprc->right = tray_cxScreen;
            if (lprc->left < g_cxScreen/2)
                lprc->left = g_cxScreen/2;
            return;

        case STICK_LEFT:
            lprc->top = tray_yScreen;
            lprc->bottom = tray_cyScreen;
            lprc->left = tray_xScreen;
            if (lprc->right > g_cxScreen/2)
                lprc->right = g_cxScreen/2;
            return;
    }

    /////
    // next we go and negotiate the size with our children.

    // Take into account changes in the border by keeping track of it.
    // if (!g_cyTrayBorders)
    //     g_cyTrayBorders = Window_GetClientGapHeight(pfc->hwndMain);

    // it doesn't matter which we take off from,
    rc = *lprc;
    rc.bottom -= g_cyTrayBorders;

    Tray_NegotiateSizing(pfc, code, &rc);

    // was there a change?  If so, adjust the lprc
    iHeight = RECTHEIGHT(rc);
    if ((iHeight+iYExtra) != RECTHEIGHT(*lprc)) {
        switch(uStuckPlace)
        {
            case STICK_TOP:
                lprc->bottom = lprc->top + iHeight + iYExtra;
                break;

            case STICK_BOTTOM:
                lprc->top = lprc->bottom - (iHeight + iYExtra);
                break;
        }
    }

    // Remember the current client gap.
    g_cyTrayBorders = iYExtra;
}

/*-------------------------------------------------------------------
** the screen size changed, and we need to adjust some stuff, mostly
** globals.  if the tray was docked, it needs to be resized, too.
**
** TRICKINESS: the handling of WM_WINDOWPOSCHANGING is used to
** actually do all the real sizing work.  this saves a bit of
** extra code here.
**-------------------------------------------------------------------*/
void Tray_ScreenSizeChange(HWND hWnd)
{
    // screen size changed, so we need to adjust globals
    g_cxScreen = GetSystemMetrics(SM_CXSCREEN);
    g_cyScreen = GetSystemMetrics(SM_CYSCREEN);

    ResizeStuckRects(g_ts.arStuckRects);

    // recompute the autohide rect
    if (g_ts.uAutoHide & AH_ON) {
        int x,y;
        x = RECTWIDTH(g_ts.rc);
        y = RECTHEIGHT(g_ts.rc);
        g_ts.rc = g_ts.arStuckRects[g_ts.uStuckPlace];

        switch(g_ts.uStuckPlace) {
            case STICK_TOP:
                g_ts.rc.bottom = g_ts.rc.top + y;
                break;

            case STICK_BOTTOM:
                g_ts.rc.top = g_ts.rc.bottom - y;
                break;

            case STICK_RIGHT:
                g_ts.rc.left = g_ts.rc.right - x;
                break;

            case STICK_LEFT:
                g_ts.rc.right = g_ts.rc.left + x;
                break;
        }

    }

    if (hWnd) {
        // fake up position globals to set "new" docking position
        // since WINDOWPOSCHANGING will be used, fake up gmove_ data
        // to make it look like things moved.
        // "current" position is faked to be some other docked state
        g_ts.uMoveStuckPlace = g_ts.uStuckPlace;
        g_ts.uStuckPlace = (g_ts.uStuckPlace + 1) % 4;

        //// set a bogus windowpos and actually repaint with the right
        //// shape/size in handling the WINDOWPOSCHANGING message

        SetWindowPos(hWnd, NULL,
                     g_ts.arStuckRects[g_ts.uMoveStuckPlace].left,
                     g_ts.arStuckRects[g_ts.uMoveStuckPlace].top,
                     g_ts.arStuckRects[g_ts.uMoveStuckPlace].right -
                     g_ts.arStuckRects[g_ts.uMoveStuckPlace].left,
                     g_ts.arStuckRects[g_ts.uMoveStuckPlace].bottom -
                     g_ts.arStuckRects[g_ts.uMoveStuckPlace].top,
                     SWP_NOZORDER | SWP_NOACTIVATE);

        // we went to the stuck rects which means we hid if AH_ON is set
        if (g_ts.uAutoHide & AH_ON)
            g_ts.uAutoHide |= AH_HIDING;
    }

    // Space for StartMenu items may be different now.
    // We'll need to rebuild the whole menu before showing it again.
    g_ts.fFavInvalid = TRUE;

    // if there's a rude app up and we're obscured, don't set the timer to rebuild
    if (!(g_ts.fAlwaysOnTop && g_ts.fRudeApp && IsHwndObscured(v_hwndTray)))
    {
        // Use the "Update Favourite" timer to do it as
        // soon as possible.
        SetTimer(v_hwndTray, IDT_FAVOURITE, 2*1000, NULL);
    }

    Tray_SizeWindows();
}

void _UpdateAlwaysOnTop(BOOL fAlwaysOnTop)
{
    //
    // The user clicked on the AlwaysOnTop menu item, we should now toggle
    // the state and update the window occordingly...
    //
    g_ts.fAlwaysOnTop = fAlwaysOnTop;
    SetWindowPos(v_hwndTray,
        g_ts.fAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
        0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

    // Make sure the screen limits are update to the new state.
    StuckSizeChange();
    AppBarNotifyAll(ABN_STATECHANGE, NULL, 0);
}


void ViewOptionsUpdateDisplay(HWND hDlg);

void _ApplyViewOptionsFromDialog(LPPROPSHEETPAGE psp, HWND hDlg)
{
    // We need to get the Cabinet structure from the property sheet info.
    LPSHELLBROWSER psb = (LPSHELLBROWSER)(psp->lParam);
    PFileCabinet pfc = IToClassN(CFileCabinet, sb, psb);
    BOOL fSmallPrev;
    BOOL fSmallNew;
    BOOL fAutoHide;

    // First check for Always on Top
    _UpdateAlwaysOnTop(IsDlgButtonChecked(hDlg, IDC_TRAYOPTONTOP));

    //
    // And change the Autohide state
    fAutoHide = IsDlgButtonChecked(hDlg, IDC_TRAYOPTAUTOHIDE);
    if (!SetAutoHideState(fAutoHide) && fAutoHide) {
        // we tried and failed.
        if (!(g_ts.uAutoHide & AH_ON)) {
            CheckDlgButton(hDlg, IDC_TRAYOPTAUTOHIDE, FALSE);
            ViewOptionsUpdateDisplay(hDlg);
        }
    }


    // show/hide the clock
    g_ts.fHideClock = !IsDlgButtonChecked(hDlg, IDC_TRAYOPTSHOWCLOCK);
    SendMessage(g_ts.hwndNotify, TNM_HIDECLOCK, 0, g_ts.fHideClock);
    Tray_SizeWindows();

    // check if it's changed
    fSmallPrev = (g_ts.fSMSmallIcons ? TRUE : FALSE);
    fSmallNew = (IsDlgButtonChecked(hDlg, IDC_SMSMALLICONS) ? TRUE : FALSE);
    if (fSmallPrev != fSmallNew)
    {
        g_ts.fSMSmallIcons = fSmallNew;
        g_ts.fFavInvalid = TRUE;
        SetTimer(v_hwndTray, IDT_FAVOURITE, 2*1000, NULL);
    }
}

//---------------------------------------------------------------------------
void ViewOptionsInitBitmaps(HWND hDlg)
{
    int i;

    for (i = 0; i <= (IDB_VIEWOPTIONSLAST - IDB_VIEWOPTIONSFIRST); i++) {
        HBITMAP hbm;

        hbm = LoadImage(hinstCabinet,
                        MAKEINTRESOURCE(IDB_VIEWOPTIONSFIRST + i),
                        IMAGE_BITMAP, 0,0, LR_LOADMAP3DCOLORS);
        hbm = (HBITMAP)SendDlgItemMessage(hDlg, IDC_VIEWOPTIONSICONSFIRST + i,
                                          STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbm);
        if (hbm)
            DeleteBitmap(hbm);
    }
}

void ViewOptionsDestroyBitmaps(HWND hDlg)
{
    int i;

    for (i = 0; i <= (IDB_VIEWOPTIONSLAST - IDB_VIEWOPTIONSFIRST); i++) {
        HBITMAP hbmOld;
        hbmOld = (HBITMAP)SendDlgItemMessage(hDlg, IDC_VIEWOPTIONSICONSFIRST + i,
                                             STM_GETIMAGE, 0,0);
        if (hbmOld)
            DeleteBitmap(hbmOld);
    }
}

void ViewOptionsUpdateDisplay(HWND hDlg)
{
    HWND hwnd;
    BOOL fShown;
    BOOL f;

    // this works by having the background bitmap having the
    // small menu, the tray in autohide/ontop mode and we lay other statics
    // over it to give the effect we want.
    SetWindowPos(GetDlgItem(hDlg, IDC_VOBASE), HWND_BOTTOM, 0, 0, 0, 0,
                 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

    ShowWindow(GetDlgItem(hDlg, IDC_VOLARGEMENU),
               !IsDlgButtonChecked(hDlg, IDC_SMSMALLICONS) ? SW_SHOWNA : SW_HIDE);

    ShowWindow(GetDlgItem(hDlg, IDC_VOTRAY),
               !(f = IsDlgButtonChecked(hDlg, IDC_TRAYOPTAUTOHIDE)) ? SW_SHOWNA : SW_HIDE);

    ShowWindow(GetDlgItem(hDlg, IDC_VOTRAYNOCLOCK),
               !f && !IsDlgButtonChecked(hDlg, IDC_TRAYOPTSHOWCLOCK) ? SW_SHOWNA : SW_HIDE);
    ShowWindow(hwnd = GetDlgItem(hDlg, IDC_VOWINDOW),
               (FALSE != (fShown = !IsDlgButtonChecked(hDlg, IDC_TRAYOPTONTOP))) ? SW_SHOWNA : SW_HIDE);

    if (fShown)
        SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0,
                     SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}


#define CXVOBASE 274
#define CYVOBASE 130

#define XVOMENU 2
#define YVOMENU 0
#define CXVOMENU 150
#define CYVOMENU 98

#define XVOTRAY 2
#define YVOTRAY 100
#define CXVOTRAY 268
#define CYVOTRAY 28

#define XVOTRAYNOCLOCK (XVOTRAY + 184)
#define YVOTRAYNOCLOCK (YVOTRAY + 3)
#define CXVOTRAYNOCLOCK 79
#define CYVOTRAYNOCLOCK 22

#define XVOWINDOW 212
#define YVOWINDOW 100
#define CXVOWINDOW 61
#define CYVOWINDOW 6

// need to do this by hand because dialog units to pixels will change,
// but the bitmaps won't
void ViewOptionsSizeControls(HWND hDlg)
{
    POINT ptBase; // coordinates of origin of VOBase.bmp
    HWND hwnd, hwnd2;

    ptBase.x = ptBase.y = 0;
    hwnd = GetDlgItem(hDlg, IDC_VOBASE);
    MapWindowPoints(hwnd, hDlg, &ptBase, 1);

    // over it to give the effect we want.
    SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, CXVOBASE, CYVOBASE,
                 SWP_NOMOVE | SWP_NOACTIVATE);

    SetWindowPos(GetDlgItem(hDlg, IDC_VOLARGEMENU), NULL,
                 ptBase.x + XVOMENU, ptBase.y + YVOMENU, CXVOMENU, CYVOMENU, SWP_NOACTIVATE | SWP_NOZORDER);
    SetWindowPos(hwnd2 = GetDlgItem(hDlg, IDC_VOTRAY), NULL,
                 ptBase.x + XVOTRAY, ptBase.y + YVOTRAY, CXVOTRAY, CYVOTRAY, SWP_NOACTIVATE);
    SetWindowPos(hwnd = GetDlgItem(hDlg, IDC_VOTRAYNOCLOCK), HWND_TOP,
                 ptBase.x + XVOTRAYNOCLOCK, ptBase.y + YVOTRAYNOCLOCK, CXVOTRAYNOCLOCK, CYVOTRAYNOCLOCK, SWP_NOACTIVATE);
    SetWindowPos(GetDlgItem(hDlg, IDC_VOWINDOW), NULL,
                 ptBase.x + XVOWINDOW, ptBase.y + YVOWINDOW, CXVOWINDOW, CYVOWINDOW, SWP_NOACTIVATE);

}


const static DWORD aTaskOptionsHelpIDs[] = {  // Context Help IDs
    IDC_VOBASE,           IDH_TASKBAR_OPTIONS_BITMAP,
    IDC_VOLARGEMENU,      IDH_TASKBAR_OPTIONS_BITMAP,
    IDC_VOTRAY,           IDH_TASKBAR_OPTIONS_BITMAP,
    IDC_VOWINDOW,         IDH_TASKBAR_OPTIONS_BITMAP,
    IDC_TRAYOPTONTOP,     IDH_TRAY_TASKBAR_ONTOP,
    IDC_TRAYOPTAUTOHIDE,  IDH_TRAY_TASKBAR_AUTOHIDE,
    IDC_SMSMALLICONS,     IDH_STARTMENU_SMALLICONS,
    IDC_TRAYOPTSHOWCLOCK, IDH_TRAY_SHOW_CLOCK,

    0, 0
};

BOOL CALLBACK TrayViewOptionsDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LPPROPSHEETPAGE psp = (LPPROPSHEETPAGE)GetWindowLong(hDlg, DWL_USER);

    switch (uMsg) {

    case WM_COMMAND:
        ViewOptionsUpdateDisplay(hDlg);
        SendMessage(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0L);
        break;

    case WM_INITDIALOG:
        SetWindowLong(hDlg, DWL_USER, lParam);

        if (g_ts.fAlwaysOnTop)
            CheckDlgButton(hDlg, IDC_TRAYOPTONTOP, TRUE);
        if (g_ts.uAutoHide & AH_ON)
            CheckDlgButton(hDlg, IDC_TRAYOPTAUTOHIDE, TRUE);
        if (g_ts.fSMSmallIcons)
            CheckDlgButton(hDlg, IDC_SMSMALLICONS, TRUE);
        if (!g_ts.fHideClock)
            CheckDlgButton(hDlg, IDC_TRAYOPTSHOWCLOCK, TRUE);

        ViewOptionsSizeControls(hDlg);
        ViewOptionsInitBitmaps(hDlg);
        ViewOptionsUpdateDisplay(hDlg);
        break;

    case WM_SYSCOLORCHANGE:
        ViewOptionsInitBitmaps(hDlg);
        return TRUE;

    case WM_DESTROY:
        ViewOptionsDestroyBitmaps(hDlg);
        break;

    case WM_NOTIFY:
        switch (((NMHDR *)lParam)->code) {
        case PSN_APPLY:
            // save settings here
            _ApplyViewOptionsFromDialog(psp, hDlg);
            return TRUE;

        case PSN_KILLACTIVE:
        case PSN_SETACTIVE:
            return TRUE;

        }
        break;

        case WM_HELP:
            WinHelp(((LPHELPINFO) lParam)->hItemHandle, NULL, HELP_WM_HELP, (DWORD)(LPTSTR) aTaskOptionsHelpIDs);
        break;

        case WM_CONTEXTMENU:
            WinHelp((HWND) wParam, NULL, HELP_CONTEXTMENU, (DWORD)(LPVOID)aTaskOptionsHelpIDs);
        break;
    }

    return FALSE;
}


// this will open  the special view info stream  for  the "tray" incarnation
// of tray, rather  than the cabinet's incarnation of the tray directory
STDMETHODIMP CDeskTray_GetViewStateStream(IShellBrowser * psb, DWORD grfMode, LPSTREAM *pStrm)
{
    CFileCabinet * this = IToClassN(CFileCabinet, sb, psb);

    // Now lets try to save away the other information associated with view.
    // We need to figure out where we will store the information.
    // If it is a local drive, we will save it in the ini file associated
    // with that directory.  If it is remote and we are opening to write
    // we will look in our cache.  If it is remote and we are opening for
    // reading, we will first see if it is in our cache and if not we
    // will read it from the remote one.


    // And call off to get the stream associated with the path.
    *pStrm = Cabinet_GetViewStreamForPidl(this->pidl, grfMode, c_szViewStreamInfo);

    return *pStrm ? S_OK : E_OUTOFMEMORY;
}

BOOL CALLBACK InitStartMenuDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
/*-------------------------------------------------------------
** Method to add view property sheet pages to the tray property
** sheet
**-------------------------------------------------------------*/
STDMETHODIMP CShellTray_AddViewPropertySheetPages(IShellBrowser * psb,
        DWORD dwReserved, LPFNADDPROPSHEETPAGE lpfn, LPARAM lParam)
{
    HPROPSHEETPAGE hpage;
    PROPSHEETPAGE psp;

    psp.dwSize = SIZEOF(psp);
    psp.dwFlags = PSP_DEFAULT;
    psp.hInstance = hinstCabinet;
    psp.pszTemplate = MAKEINTRESOURCE(DLG_TRAY_VIEW_OPTIONS);
    psp.pfnDlgProc = TrayViewOptionsDlgProc;
    psp.lParam = (LPARAM)psb;
    hpage = CreatePropertySheetPage(&psp);
    if (hpage)
        lpfn(hpage, lParam);

    psp.pszTemplate = MAKEINTRESOURCE(DLG_STARTMENU_CONFIG);
    psp.pfnDlgProc = InitStartMenuDlgProc;
    psp.lParam = (LPARAM)psb;
    hpage = CreatePropertySheetPage(&psp);
    if (hpage)
        lpfn(hpage, lParam);

    return(S_OK);
}

void _SaveTrayAndDesktop(void)
{
    WINDOWPLACEMENT wp;
    TVSD tvsd;
    FOLDERSETTINGS fs;
    PFileCabinet pfc;

    DebugMsg(DM_TRACE, TEXT("Saving desktop state"));

    if (SHRestricted(REST_NOSAVESET))
    {
        return;
    }

    Cabinet_SaveState(v_hwndDesktop, NULL, TRUE, FALSE, FALSE);
    pfc = GetPFC(v_hwndDesktop);
    pfc->psv->lpVtbl->GetCurrentInfo(pfc->psv, &fs);
    Reg_SetStruct(g_hkeyExplorer, c_szCabinetDeskView, c_szSettings, &fs, SIZEOF(fs));


    // And now the tray.
    DebugMsg(DM_TRACE, TEXT("Saving tray state"));

    HotkeyList_Save();

    wp.length = SIZEOF(wp);
    GetWindowPlacement(v_hwndTray, &wp);

    Cabinet_SaveState(v_hwndTray, &wp, TRUE, FALSE, FALSE);

    // These have now been combined into one structure - Review how to put into stream
    tvsd.cxScreen = g_cxScreen;
    tvsd.cyScreen = g_cyScreen;

    // Now get the sizes for the four edges.
    tvsd.dyTop = g_ts.arStuckRects[STICK_TOP].bottom - tray_yScreen;
    tvsd.dyBottom = tray_cyScreen - g_ts.arStuckRects[STICK_BOTTOM].top;
    tvsd.dxLeft = g_ts.arStuckRects[STICK_LEFT].right - tray_xScreen;
    tvsd.dxRight = tray_cxScreen - g_ts.arStuckRects[STICK_RIGHT].left;

    // And also stuff for autohide
    tvsd.uAutoHide = g_ts.uAutoHide;
    tvsd.rclAutoHide.left = g_ts.rc.left;
    tvsd.rclAutoHide.right = g_ts.rc.right;
    tvsd.rclAutoHide.top = g_ts.rc.top;
    tvsd.rclAutoHide.bottom = g_ts.rc.bottom;

    // The rest.
    tvsd.uStuckPlace = g_ts.uStuckPlace;
    tvsd.dwFlags = g_ts.fAlwaysOnTop ? TVSD_TOPMOST : 0;
    if (g_ts.fSMSmallIcons) tvsd.dwFlags |= TVSD_SMSMALLICONS;
    if (g_ts.fHideClock) tvsd.dwFlags |= TVSD_HIDECLOCK;

    // Sanity check.
    tvsd.dwSize = SIZEOF(tvsd);

    // Save for now in Stuck rects.
    Reg_SetStruct(g_hkeyExplorer, c_szCabinetStuckRects, c_szSettings, &tvsd, SIZEOF(tvsd));
}

void SlideWindow(HWND hwnd, RECT *prc)
{
    RECT rcOld;
    RECT rcNew;
    int  x,y,dx,dy,dt,t,t0;
    BOOL fShow;
    HANDLE me;
    int priority;

    rcNew = *prc;

    if (g_fDragFullWindows && g_dtSlideShow>0 && g_dtSlideHide>0) {

        GetWindowRect(hwnd, &rcOld);

        fShow = (rcNew.bottom-rcNew.top) > (rcOld.bottom-rcOld.top) ||
                (rcNew.right-rcNew.left) > (rcOld.right-rcOld.left);

        dx = (rcNew.right-rcOld.right) + (rcNew.left - rcOld.left);
        dy = (rcNew.bottom-rcOld.bottom) + (rcNew.top - rcOld.top);

        Assert((rcOld.right-rcNew.right)==0 || (rcOld.left-rcNew.left)==0);
        Assert((rcOld.bottom-rcNew.bottom)==0 || (rcOld.top-rcNew.top)==0);
        Assert(dx == 0 || dy == 0);

        if (fShow)
        {
            rcOld = rcNew;
            OffsetRect(&rcOld, -dx, -dy);
            SetWindowPos(hwnd, NULL, rcOld.left, rcOld.top,
                rcOld.right - rcOld.left, rcOld.bottom - rcOld.top,
                SWP_NOZORDER|SWP_NOACTIVATE|SWP_DRAWFRAME);

            dt = g_dtSlideShow;
        }
        else
        {
            dt = g_dtSlideHide;
        }

        me = GetCurrentThread();
        priority = GetThreadPriority(me);
        SetThreadPriority(me, THREAD_PRIORITY_HIGHEST);

        t0 = GetTickCount();

        while ((t = GetTickCount()) < t0 + dt)
        {
            x = rcOld.left + (dx) * (t - t0) / dt;
            y = rcOld.top  + (dy)  * (t - t0) / dt;

            SetWindowPos(hwnd, NULL, x, y, 0, 0,
                SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
            if (fShow)
                UpdateWindow(hwnd);
        }

        SetThreadPriority(me, priority);
    }

    SetWindowPos(hwnd, NULL, rcNew.left, rcNew.top,
        rcNew.right - rcNew.left, rcNew.bottom - rcNew.top,
        SWP_NOZORDER|SWP_NOACTIVATE|SWP_DRAWFRAME);

    StuckSizeChange();
    AppBarNotifyAll(ABN_POSCHANGED, NULL, 0);
}

void DoTrayUnhide()
{
    g_ts.fSelfSizing = TRUE;
    DAD_ShowDragImage(FALSE);   // unlock the drag sink if we are dragging.
    SlideWindow(v_hwndTray, &g_ts.rc);
////MoveWindow(v_hwndTray, g_ts.rc.left, g_ts.rc.top, g_ts.rc.right - g_ts.rc.left, g_ts.rc.bottom - g_ts.rc.top, TRUE);
    UpdateWindow(v_hwndTray);
    DAD_ShowDragImage(TRUE);    // restore the lock state.
    g_ts.fSelfSizing = FALSE;

    ClockCtl_HandleTrayHide(FALSE);

}

void TrayHide()
{
    RECT rcNew, rcOld;

    GetWindowRect(v_hwndTray, &rcOld);

    rcNew = rcOld;

    // bugbug, what we really want to do is to force a bigger size,
    // not just punt out on saving this new size.
    if ((STUCK_HORIZONTAL(g_ts.uStuckPlace) &&
         (RECTHEIGHT(rcOld) > g_cyFrame)) ||
        (!STUCK_HORIZONTAL(g_ts.uStuckPlace) &&
         (RECTWIDTH(rcOld) > g_cxFrame)))
        g_ts.rc = rcOld;


    switch (g_ts.uStuckPlace) {
        case STICK_LEFT:
            rcNew.right = rcNew.left + g_cxFrame;
            break;

        case STICK_RIGHT:
            rcNew.left = rcNew.right - g_cxFrame;
            break;

        case STICK_TOP:
            rcNew.bottom = rcNew.top + g_cyFrame;
            break;

        case STICK_BOTTOM:
            rcNew.top = rcNew.bottom - g_cyFrame;
            break;
    }
    g_ts.uAutoHide = AH_ON | AH_HIDING;
    g_ts.fSelfSizing = TRUE;

    DAD_ShowDragImage(FALSE);   // unlock the drag sink if we are dragging.
    SlideWindow(v_hwndTray, &rcNew);
////MoveWindow(v_hwndTray, rcNew.left, rcNew.top, rcNew.right - rcNew.left, rcNew.bottom - rcNew.top, TRUE);
    DAD_ShowDragImage(TRUE);    // restore the lock state.

    ClockCtl_HandleTrayHide(TRUE);

    g_ts.fSelfSizing = FALSE;

}

BOOL SetAutoHideState (BOOL fAutoHide)
{
    if (!IAppBarSetAutoHideBar(v_hwndTray, fAutoHide, g_ts.uStuckPlace)) {
        // unable to set it because there's another one there already.
        ShellMessageBox(hinstCabinet, v_hwndTray, MAKEINTRESOURCE(IDS_ALREADYAUTOHIDEBAR),
                        MAKEINTRESOURCE(IDS_TASKBAR),MB_OK|MB_ICONSTOP);

        return FALSE;
    }

    if (fAutoHide && !(g_ts.uAutoHide & AH_ON)) {
        TrayHide();
    } else if (!fAutoHide & (g_ts.uAutoHide & AH_ON)) {
        g_ts.uAutoHide = 0;
        KillTimer(v_hwndTray,  IDT_AUTOHIDE);
        DoTrayUnhide();
        LimitScreenSize(&g_ts.arStuckRects[g_ts.uStuckPlace]);
    } else {
        return TRUE;
    }

    AppBarNotifyAll(ABN_STATECHANGE, NULL, 0);
    return TRUE;
}

void Tray_HandleEnterMenuLoop()
{
    // kill the timer when we're in the menu loop so that we don't
    // pop done while browsing the menus.
    if (g_ts.uAutoHide & AH_ON) {
        KillTimer(v_hwndTray,  IDT_AUTOHIDE);
    }
}

void SetAutoHideTimer()
{
    if (g_ts.uAutoHide & AH_ON) {
        SetTimer(v_hwndTray, IDT_AUTOHIDE, 500, NULL);
    }
}

void Tray_HandleExitMenuLoop()
{
    // when we leave the menu stuff, start checking again.
    SetAutoHideTimer();
}

void TrayUnhide()
{
    // handle autohide
    if ((g_ts.uAutoHide & AH_ON) &&
        (g_ts.uAutoHide & AH_HIDING)) {
        DoTrayUnhide();
        g_ts.uAutoHide &= ~AH_HIDING;
        SetAutoHideTimer();

        if (g_ts.fShouldResize) {
            Assert(!(g_ts.uAutoHide & AH_HIDING));
            Tray_SizeWindows();
            g_ts.fShouldResize = FALSE;
        }
    }
}

void TraySetUnhideTimer(LONG x, LONG y)
{
    // handle autohide
    if ((g_ts.uAutoHide & AH_ON) &&
        (g_ts.uAutoHide & AH_HIDING))
    {
        LONG dx = x-g_ts.ptLastHittest.x;
        LONG dy = y-g_ts.ptLastHittest.y;
        LONG rr = dx*dx + dy*dy;
        LONG dd = GetSystemMetrics(SM_CXDOUBLECLK) * GetSystemMetrics(SM_CYDOUBLECLK);

        if (rr > dd) {
            SetTimer(v_hwndTray, IDT_AUTOUNHIDE, 50, NULL);
            g_ts.ptLastHittest.x = x;
            g_ts.ptLastHittest.y = y;
        }
    }
}

//---------------------------------------------------------------------------
void RebuildEntireMenu(PFileCabinet pfc)
{
    DebugMsg(DM_IANELHK, TEXT("c.rfm: Cleaning up Frequent menu."));
    // Delete all the start menu items.
    pfc->bMainMenuInit = TRUE;
    UnregisterNotify(g_ts.uFavNotify);
    g_ts.uFavNotify = 0;
    UnregisterNotify(g_ts.uCommonFavNotify);
    g_ts.uCommonFavNotify = 0;

    StartMenu_Destroy(pfc);
    StartMenu_Build(pfc);
    pfc->bMainMenuInit = FALSE;
    DebugMsg(DM_IANELHK, TEXT("c.rfm: Done."));
    g_ts.fFavInvalid = FALSE;
    StartMenu_ControlBkgThread(SMCT_RESTART);
}

//----------------------------------------------------------------------------
void StartButton_ResetAndSize(PFileCabinet pfc)
{
    HBITMAP hbmOld, hbmNew;

    DebugMsg(DM_IANELHK, TEXT("c.sb_rs: Recreating start button."));
    hbmOld = (HBITMAP)SendMessage(g_ts.hwndStart, BM_GETIMAGE, IMAGE_BITMAP, 0);
    if (hbmOld)
    {
        hbmNew = CreateStartBitmap(pfc->hwndMain);
        if (hbmNew)
        {
            SendMessage(g_ts.hwndStart, BM_SETIMAGE, IMAGE_BITMAP, (DWORD)hbmNew);
            DeleteObject(hbmOld);
        }
    }
    AlignStartButton(pfc->hwndToolbar);
    Tray_SizeWindows();
}

//---------------------------------------------------------------------------
void Tray_OnDelayWinIniChange(PFileCabinet pfc)
{

    DebugMsg(DM_TRACE, TEXT("c.t_odwic: Handling win ini change."));

    TrayUnhide();
    Tray_VerifySize(pfc, TRUE);

    StartButton_ResetAndSize(pfc);
    // We'll need to rebuild the whole menu before showing it again.
    g_ts.fFavInvalid = TRUE;
    // Use the "Update Favourite" timer to do it as
    // soon as possible.
    SetTimer(v_hwndTray, IDT_FAVOURITE, 2*1000, NULL);
}

//---------------------------------------------------------------------------
void Tray_HandleTimer(PFileCabinet pfc, WPARAM wTimerID)
{
    POINT pt;
    RECT rc;

    switch (wTimerID) {
        case IDT_PROGRAMS:
        case IDT_RECENT:
            if (!pfc->bMainMenuInit)
            {
                StartMenu_ControlBkgThread(SMCT_STOP);
                KillTimer(v_hwndTray,  wTimerID);
                StartMenu_ControlBkgThread((wTimerID == IDT_PROGRAMS) ?
                                           SMCT_FILLPROGRAMSONLY : SMCT_FILLRECENTONLY);
            }
            break;

        case IDT_REBUILDMENU:

            if (!pfc->bMainMenuInit)
            {
                DebugMsg(DM_TRACE, TEXT("c.ht: rebuilding start menu."));
                StartMenu_ControlBkgThread(SMCT_STOP);
                KillTimer(v_hwndTray,  wTimerID);
                RebuildEntireMenu(pfc);
            } else {
               DebugMsg(DM_TRACE, TEXT("c.ht: waiting to rebuild start menu."));
            }
            break;

        case IDT_FAVOURITE:
            if (!pfc->bMainMenuInit)
            {
                StartMenu_ControlBkgThread(SMCT_STOP);
                RebuildEntireMenu(pfc);
            }
            if (!g_ts.fFavInvalid)
                KillTimer(v_hwndTray,  wTimerID);
            break;

#ifdef DELAYWININICHANGE
        case IDT_DELAYWININICHANGE:
            KillTimer(pfc->hwndMain, wTimerID);
            Tray_OnDelayWinIniChange(pfc);
            break;
#endif

        case IDT_DESKTOP:
            DebugMsg(DM_TRACE, TEXT("c.t_ht: IDT_DESKTOP"));
            StartMenu_ControlBkgThread(SMCT_STOP);
            KillTimer(pfc->hwndMain, wTimerID);
            StartMenu_ControlBkgThread(SMCT_DESKTOPHOTKEYSONLY);
            break;
    }

    if  (g_ts.uAutoHide & AH_ON) {

        if (g_ts.fSysSizing)
            return;

        switch (wTimerID) {
            case IDT_AUTOHIDE:
                // handle autohide
                if (!g_ts.fSysSizing && !(g_ts.uAutoHide & AH_HIDING)) {
                    HWND hwndA = GetActiveWindow();
                    GetCursorPos(&pt);
                    rc = g_ts.rc;
                    InflateRect(&rc, g_cxEdge * 4, g_cyEdge*4);
                    if (!PtInRect(&rc, pt) &&
                        hwndA != v_hwndTray &&
                        !SendMessage(pfc->hwndView, TM_SYSMENUCOUNT, 0, 0L) &&
                        (hwndA == NULL || GetWindowOwner(hwndA) != v_hwndTray)) {
                        KillTimer(v_hwndTray, wTimerID);
                        TrayHide();
                    }
                }
                break;

            case IDT_AUTOUNHIDE:
                // handle autounhide
                KillTimer(v_hwndTray, wTimerID);
                g_ts.ptLastHittest.x = -0x0fff;
                g_ts.ptLastHittest.y = -0x0fff;
                GetWindowRect(v_hwndTray, &rc);
                if (g_ts.uAutoHide & AH_HIDING) {
                    GetCursorPos(&pt);
                    if (PtInRect(&rc, pt))
                        TrayUnhide();
                }
                break;
        }
    }
}

//---------------------------------------------------------------------------
BOOL Menu_GetItemData(HMENU hmenu, UINT iItem)
{
    MENUITEMINFO mii;

    mii.cbSize = SIZEOF(MENUITEMINFO);
    mii.fMask = MIIM_DATA | MIIM_STATE;
    mii.cch = 0;     // just in case

    if (GetMenuItemInfo(hmenu, iItem, TRUE, &mii))
    {
        return mii.dwItemData;
    }

    return 0;
}

//---------------------------------------------------------------------------
LRESULT Tray_HandleInitMenuPopup(HMENU hmenuPopup)
{
    LPITEMIDLIST pidl, pidlAlt = NULL;
    UINT wID;
    int iSpecialID;
    UINT uMsg = 0;
    ULONG *puNotify = NULL;
    LPFileCabinet pfc;

    // DebugMsg(DM_TRACE, "hmenupopup = %d, id = %d", hmenuPopup, GetMenuItemID(hmenuPopup, 0));

    if (!hmenuPopup)
        return 1;

    wID = GetMenuItemID(hmenuPopup, 0);
    switch (wID)
    {
        case IDM_RECENT:
            WaitForRecent();
        case IDM_PROGRAMS:
            if (FileMenu_InitMenuPopup(hmenuPopup))
                break;
        case IDM_RECENTINIT:
        case IDM_PROGRAMSINIT:

            if (wID == IDM_PROGRAMSINIT)
            {
                iSpecialID = CSIDL_PROGRAMS;
                wID = IDM_PROGRAMS;
                if (!g_ts.uProgNotify) {
                    uMsg = WMTRAY_PROGCHANGE;
                    puNotify = &g_ts.uProgNotify;
                }

                pidlAlt = SHCloneSpecialIDList(NULL, CSIDL_COMMON_PROGRAMS, FALSE);
            }
            else
            {
                wID = IDM_RECENT;
                iSpecialID = CSIDL_RECENT;
                if (!g_ts.uRecentNotify) {
                    uMsg = WMTRAY_RECCHANGE;
                    puNotify = &g_ts.uRecentNotify;
                }
            }

            pidl = SHCloneSpecialIDList(NULL, iSpecialID, FALSE);
            if (!pidl)
                goto ExitAndFree;

            // Make sure we get notified of changes.
            if (puNotify)
            {
                pfc = g_pfcTray;
                Assert(pfc);
                if (pfc)
                    *puNotify = RegisterNotify(pfc->hwndMain, uMsg, pidl);
            }

            // Remove our fake initialisation menu item.
            // NB It's possible to have a filemenu here so we have to be
            // careful to check before we delete the initialisation item.
            // We should have a FileMenu_IsFileMenu() API but this is
            // good enough for now.
            if (!Menu_GetItemData(hmenuPopup, 0))
            {
                DeleteMenu(hmenuPopup, 0, MF_BYPOSITION);
            }

            // Build the proper menu.
            FileMenu_DeleteAllItems(hmenuPopup);
            FileMenu_AddFilesForPidl(hmenuPopup, 0, wID, pidl, FMF_NONE, g_CabState.fMenuEnumFilter, FileMenu_Callback);

            if (pidlAlt) {

                if (!SHRestricted(REST_NOCOMMONGROUPS)) {

                    FileMenu_AppendFilesForPidl(hmenuPopup, pidlAlt, TRUE);

                    if (wID == IDM_PROGRAMS) {
                        if (!g_ts.uCommonProgNotify) {
                            if (g_pfcTray) {
                                g_ts.uCommonProgNotify = RegisterNotify(g_pfcTray->hwndMain,
                                                           WMTRAY_COMMONPROGCHANGE,
                                                           pidlAlt);
                            }
                        }
                    }
                }

            }

ExitAndFree:
            if (pidlAlt)
                ILFree(pidlAlt);

            if (pidl)
                ILFree(pidl);

            break;

    default:
        {
            FileMenu_InitMenuPopup(hmenuPopup);
            break;
        }
    }
    return 1;
}

//---------------------------------------------------------------------------
LRESULT Tray_HandleMeasureItem(HWND hwnd, LPMEASUREITEMSTRUCT lpmi)
{
    return FileMenu_MeasureItem(hwnd, lpmi);
}

//---------------------------------------------------------------------------
LRESULT Tray_HandleDrawItem(HWND hwnd, LPDRAWITEMSTRUCT lpdi)
{
    return FileMenu_DrawItem(hwnd, lpdi);
}


#if 0
const TCHAR rszRecent[]     = TEXT("IDM_RECENT");
const TCHAR rszFind[]       = TEXT("IDM_FIND");
const TCHAR rszHelp[]       = TEXT("IDM_HELPSEARCH");
const TCHAR rszPrograms[]   = TEXT("IDM_PROGRAMS");
const TCHAR rszControls[]   = TEXT("IDM_CONTROLS");
const TCHAR rszExit[]       = TEXT("IDM_EXITWIN");
const TCHAR rszPrinters[]   = TEXT("IDM_PRINTERS");
const TCHAR rszStart[]      = TEXT("IDM_STARTMENU");
const TCHAR rszMyComp[]     = TEXT("IDM_MYCOMPUTER");
const TCHAR rszProgInit[]   = TEXT("IDM_PROGRAMSINIT");
const TCHAR rszRecentInit[] = TEXT("IDM_RECENTINIT");
const TCHAR rszUnknown[]    = TEXT("[UNKNOWN]");

LPCTSTR MenuName( UINT id )
{
    switch( id )
    {

    case IDM_RECENT:
        return rszRecent;

    case IDM_FIND:
        return rszFind;

    case IDM_HELPSEARCH:
        return rszHelp;

    case IDM_PROGRAMS:
        return rszPrograms;

    case IDM_CONTROLS:
        return rszControls;

    case IDM_EXITWIN:
        return rszExit;

    case IDM_PRINTERS:
        return rszPrinters;

    case IDM_STARTMENU:
        return rszStart;

    case IDM_MYCOMPUTER:
        return rszMyComp;

    case IDM_PROGRAMSINIT:
        return rszProgInit;

    case IDM_RECENTINIT:
        return rszRecentInit;

    }

    return rszUnknown;
}
#endif

//---------------------------------------------------------------------------
// Search for the first sub menu of the given menu, who's first item's ID
// is id. Returns NULL, if nothing is found.
HMENU Menu_FindSubMenuByFirstID(HMENU hmenu, UINT id)
{
        int cMax, c;
        HMENU hmenuSub;
        MENUITEMINFO mii;

        Assert(hmenu);

    // REVIEW There's a bug in the thunks such that GMIC() returns
    // 0x0000ffff for an invalid menu which gets us confused.
    if (!IsMenu(hmenu))
        return NULL;

    // Search all items.
    mii.cbSize = SIZEOF(mii);
    mii.fMask = MIIM_ID;
    cMax = GetMenuItemCount(hmenu);
    for (c=0; c < cMax; c++)
    {
        // Is this item a submenu?
        hmenuSub = GetSubMenu(hmenu, c);
        if (hmenuSub && GetMenuItemInfo(hmenuSub, 0, TRUE, &mii))
        {
            if (mii.wID == id)
            {
                // Found it!
                return hmenuSub;
            }
        }
    }

    return NULL;
}

//---------------------------------------------------------------------------
#define CMD_ID_FIRST    1
#define CMD_ID_LAST     0x7fff

//---------------------------------------------------------------------------
BOOL _ExecItemByPidls(HWND hwnd, LPITEMIDLIST pidlFolder, LPITEMIDLIST pidlItem)
{
        IShellFolder *psf;
        LPCONTEXTMENU pcm;
        HMENU hmenu;
        UINT idCmd;
        BOOL fRes = FALSE;
        TCHAR szPath[MAX_PATH];

        if (pidlFolder && pidlItem)
        {
            if (SUCCEEDED(s_pshfRoot->lpVtbl->BindToObject(s_pshfRoot,
                                                                    pidlFolder, NULL, &IID_IShellFolder, &psf)))
            {
                if (SUCCEEDED(psf->lpVtbl->GetUIObjectOf(psf, v_hwndTray, 1, &pidlItem, &IID_IContextMenu, 0, &pcm)))
                {
                    if (pcm)
                    {
                        hmenu = CreatePopupMenu();
                        if (hmenu)
                        {
                            if (SUCCEEDED(pcm->lpVtbl->QueryContextMenu(pcm, hmenu, 0, CMD_ID_FIRST, CMD_ID_LAST,
                                                                        CMF_DEFAULTONLY))) {
                                if (hmenu)
                                {
                                    idCmd = GetMenuDefaultItem(hmenu, MF_BYCOMMAND, 0);
                                    if (idCmd)
                                    {
                                        CMINVOKECOMMANDINFOEX ici = {
                                            SIZEOF(CMINVOKECOMMANDINFOEX),
                                            CMIC_MASK_ASYNCOK,
                                            v_hwndTray,
                                            (LPSTR)MAKEINTRESOURCE(idCmd - CMD_ID_FIRST),
                                            NULL, NULL,
                                            SW_NORMAL,
                                        };

                                        pcm->lpVtbl->InvokeCommand(pcm,
                                                   (LPCMINVOKECOMMANDINFO)&ici);
                                        fRes = TRUE;
                                    }
                                    DestroyMenu(hmenu);
                                }
                            }
                        }
                        // Release our use of the context menu
                        pcm->lpVtbl->Release(pcm);
                    }
                }
                psf->lpVtbl->Release(psf);
            } else {
                SHGetPathFromIDList(pidlFolder, szPath);
                ShellMessageBox(hinstCabinet, hwnd, MAKEINTRESOURCE(IDS_CANTFINDSPECIALDIR),
                                NULL, MB_ICONEXCLAMATION, szPath);
            }
        }
        return fRes;
}

//---------------------------------------------------------------------------
LRESULT FileMenu_HandleCommand(HWND hwnd, WPARAM wparam)
{
    LPITEMIDLIST pidlFolder = NULL;
    LPITEMIDLIST pidlItem = NULL;
    BOOL fRes;
    HMENU hmenu;
    DECLAREWAITCURSOR;

    SetWaitCursor();
    hmenu = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, wparam);
    FileMenu_GetLastSelectedItemPidls(hmenu, &pidlFolder, &pidlItem);
    fRes = _ExecItemByPidls(hwnd, pidlFolder, pidlItem);
    ILFree(pidlFolder);
    ILFree(pidlItem);
    ResetWaitCursor();
#ifdef DEBUG
    if (!fRes)
    {
        DebugMsg(DM_ERROR, TEXT("t.fm_hc: Can't exec command."));
    }
#endif
    return fRes;
}

//---------------------------------------------------------------------------
LRESULT Tray_HandleDestroy(PFileCabinet pfc)
{
    HMENU hmenuSub;
    MINIMIZEDMETRICS mm;

    mm.cbSize = SIZEOF(mm);
    SystemParametersInfo(SPI_GETMINIMIZEDMETRICS, SIZEOF(mm), &mm, FALSE);
    mm.iArrange &= ~ARW_HIDE;
    SystemParametersInfo(SPI_SETMINIMIZEDMETRICS, SIZEOF(mm), &mm, FALSE);

    PrintNotify_Exit();
    CStartDropTarget_Revoke(pfc);

    // HotkeyList_Save();

    if (g_ts.pPositions)
        DestroySavedWindowPositions();

    if (g_ts.hBIOS!=INVALID_HANDLE_VALUE)
        CloseHandle(g_ts.hBIOS);

    if (g_ts.hVPowerD!=INVALID_HANDLE_VALUE)
        CloseHandle(g_ts.hVPowerD);

    {
        // Cleanup notify handlers.
        UnregisterNotify(g_ts.uProgNotify);
        UnregisterNotify(g_ts.uFavNotify);
        UnregisterNotify(g_ts.uRecentNotify);
        UnregisterNotify(g_ts.uDesktopNotify);
        UnregisterNotify(g_ts.uCommonDesktopNotify);
        UnregisterNotify(g_ts.uCommonProgNotify);
        UnregisterNotify(g_ts.uCommonFavNotify);

        // Cleanup programs
        hmenuSub = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_PROGRAMS);
        if (hmenuSub)
        {
            FileMenu_DeleteAllItems(hmenuSub);
        }
        else
        {
            DebugMsg(DM_TRACE, TEXT("c.t_hd: Can't find Programs menu."));
        }
        // Cleanup recent docs.
        hmenuSub = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_RECENT);
        if (hmenuSub)
        {
            FileMenu_DeleteAllItems(hmenuSub);
        }
        else
        {
            DebugMsg(DM_TRACE, TEXT("c.t_hd: Can't find RecentDocs."));
        }
    }

    if (pfc) {
        if (pfc->pcmFind)
            pfc->pcmFind->lpVtbl->Release(pfc->pcmFind);
        DeskTray_DestroyShellView(pfc);
    }


    if (g_ts.hwndTrayTips) {
        DestroyWindow(g_ts.hwndTrayTips);
        g_ts.hwndTrayTips = NULL;
    }

    // REVIEW
    PostQuitMessage(0);

    if (g_ts.hbmpStartBkg)
        DeleteBitmap(g_ts.hbmpStartBkg);

    v_hwndTray = NULL;
    g_ts.hwndStart = NULL;
    return 0;
}

void InvalidateRebuildMenu(WORD wID, HMENU hmenuSub)
{
    if (hmenuSub)
    {
        PFileCabinet pfc = g_pfcTray;
        if (pfc->bMainMenuInit) {
            // HACK Don't destroy the menu if we're showing it - just nuke the whole thing.
            SetTimer(v_hwndTray, IDT_REBUILDMENU, 1000, NULL);
        } else {
            StartMenu_ControlBkgThread(SMCT_STOP);
            FileMenu_Invalidate(hmenuSub);
            // set a timer to fill this in later...
            // do it later in case of multiple changes and also
            // because we want to return immediately and do the
            // updates without blocking someone else (or the filesys notification)
            // NB Use a lomger timeout if we're doing DDE. ExcelNT has a 5
            // second timeout on DDE. If we're doing updates then sometimes we
            // don't get back in time.
            if (g_hwndDde)
                SetTimer(v_hwndTray, wID, 10 * 1000, NULL);
            else
                SetTimer(v_hwndTray, wID, 3 * 1000, NULL);
        }
    }
}

void TrayProgramsInvalidateMenu(HMENU hmenuSub, LPCITEMIDLIST pidl, LPCITEMIDLIST pidlPrograms)
{
    LPITEMIDLIST pidlChild;
    HMENU hmenu;

    pidlChild = ILFindChild(pidlPrograms, pidl);
    if (pidlChild) {

        pidlChild = ILClone(pidlChild);
        ILRemoveLastID(pidlChild);
        hmenu = FileMenu_FindSubMenuByPidl(hmenuSub, pidlChild);
        // doing this will catch the case of "everything else"
        // and just nuke the whole menu tree if we couldn't find a menu
        // for this pidl
        if (!hmenu)
            hmenu = hmenuSub;

        InvalidateRebuildMenu(IDM_PROGRAMS, hmenu);

        ILFree(pidlChild);
    }
}

void TrayRevertMenuToInitial(HMENU hmenuSub, UINT idMenu, UINT idTimer)
{
    ULONG *puNotify = (idMenu == IDM_PROGRAMSINIT ? &g_ts.uProgNotify : &g_ts.uRecentNotify);

    if (idMenu == IDM_PROGRAMSINIT)
        puNotify = &g_ts.uProgNotify;

    else
        puNotify = &g_ts.uRecentNotify;

    UnregisterNotify(*puNotify);
    *puNotify = 0;

    FileMenu_DeleteAllItems(hmenuSub);
    InsertMenu(hmenuSub, 0, MF_BYPOSITION, idMenu, c_szNULL);
    SetTimer(v_hwndTray, idTimer, 10 * 1000, NULL);
}

void TrayUpdateProgramsMenu(LPARAM lEvent, LPCITEMIDLIST *ppidl, BOOL bCommon)
{
    LPITEMIDLIST pidlPrograms;
    HMENU hmenuSub;

    DebugMsg(DM_IANELHK, TEXT("c.t_hfsn: Cleaning up Programs or Recent menu."));

    hmenuSub = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_PROGRAMS);

    if (hmenuSub) {

        pidlPrograms = SHCloneSpecialIDList(NULL,
                                            bCommon ? CSIDL_COMMON_PROGRAMS : CSIDL_PROGRAMS,
                                            FALSE);
        if (!pidlPrograms)
            return;

        if (ILIsEqual(pidlPrograms, ppidl[0]) ||
            ((lEvent & SHCNE_RENAMEFOLDER) && ILIsEqual(pidlPrograms, ppidl[1]))) {

            DebugMsg(DM_TRACE, TEXT("Deleting all programs menus"));
            TrayRevertMenuToInitial(hmenuSub, IDM_PROGRAMSINIT, IDM_PROGRAMS);
        } else {

            TrayProgramsInvalidateMenu(hmenuSub, ppidl[0], pidlPrograms);

            if (lEvent & (SHCNE_RENAMEITEM | SHCNE_RENAMEFOLDER)) {
                TrayProgramsInvalidateMenu(hmenuSub, ppidl[1], pidlPrograms);
            }

        }

        ILFree(pidlPrograms);
    }
}

//---------------------------------------------------------------------------
LRESULT Tray_HandleFSNotify(UINT uMsg, PFileCabinet pfc, LPITEMIDLIST * ppidl, LPARAM lEvent)
{
    HMENU hmenuSub;
    WORD wID = 0;

    DebugMsg(DM_TRACE, TEXT("Tray_HandleFSNotify : enter!!"));

    // Roach the start menu if the icon cache is getting re-ordered.
    if ((lEvent == SHCNE_UPDATEIMAGE) && ppidl && ppidl[0])
    {
        if (*(int UNALIGNED *)((BYTE *)ppidl[0] + 2) == -1)
        {
            DebugMsg(DM_TRACE, TEXT("c.t_hfsn: Update image of -1."));
            g_ts.fFavInvalid = TRUE;
            SetTimer(v_hwndTray, IDT_FAVOURITE, 1000, NULL);
        }
        return 1;
    }

    switch (uMsg) {
    case WMTRAY_PRINTCHANGE:
        PrintNotify_HandleFSNotify(pfc->hwndMain, ppidl, lEvent);
        break;

    case WMTRAY_FAVCHANGE:
    case WMTRAY_COMMONFAVCHANGE:
    {
        LPITEMIDLIST pidlProgs;

        pidlProgs = SHCloneSpecialIDList(NULL,
                    ((uMsg == WMTRAY_FAVCHANGE) ? CSIDL_PROGRAMS : CSIDL_COMMON_PROGRAMS),
                     TRUE);

        // If the Programs folder is under the StartMenu (usual case) then we'll
        // get a FAVCHANGE everytime something in the Programs folder changes.
        // FAVCHANGES are kinda piggy so filter out spurious ones here.
        if (pidlProgs && ppidl)
        {
            if ((ppidl[0] && !ILIsParent(pidlProgs, ppidl[0], FALSE))
                || (ppidl[1] && !ILIsParent(pidlProgs, ppidl[1], FALSE)))
            {
                DebugMsg(DM_TRACE, TEXT("WMTRAY_FAVCHANGE"));
                g_ts.fFavInvalid = TRUE;
                // the timer is to collect up the changes...
                SetTimer(v_hwndTray, IDT_FAVOURITE, 1000, NULL);
            }
            ILFree(pidlProgs);
        }
        break;
    }

    case WMTRAY_RECCHANGE:
        hmenuSub = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, IDM_RECENT);
        if (hmenuSub) {
            TrayRevertMenuToInitial(hmenuSub, IDM_RECENTINIT, IDM_RECENT);
        }
        break;

    case WMTRAY_PROGCHANGE:
        TrayUpdateProgramsMenu(lEvent, ppidl, FALSE);
        break;

    case WMTRAY_COMMONPROGCHANGE:
        TrayUpdateProgramsMenu(lEvent, ppidl, TRUE);
        break;

    case WMTRAY_DESKTOPCHANGE:
        DebugMsg(DM_TRACE, TEXT("c.t_hfs: Desktop change."));
        SetTimer(v_hwndTray, IDT_DESKTOP, 2000, NULL);
        break;

    default:
        Assert(0);
        break;
    }
    return 1;
}


void Tray_ActAsSwitcher(PFileCabinet pfc)
{

    if (g_ts.uModalMode) {

        if (g_ts.uModalMode != MM_SHUTDOWN) {
            SwitchToThisWindow(GetLastActivePopup(pfc->hwndMain), TRUE);
        }
        MessageBeep(0);

    } else {
        HWND hwndForeground;
#ifdef DEBUG
        static int s_iRecurse = 0;
        s_iRecurse++;
        Assert(s_iRecurse < 100);
        DebugMsg(DM_TRACE, TEXT("s_iRecurse = %d"), s_iRecurse);
#endif

        hwndForeground = GetForegroundWindow();
        // only do the button once we're the foreground dude.
        if ((hwndForeground == v_hwndTray) && (GetActiveWindow() == v_hwndTray)) {

            // This pushes the start button and causes the start menu to popup.
            SendMessage(GetDlgItem(v_hwndTray, IDC_START), BM_SETSTATE, TRUE, 0);
#ifdef DEBUG
            s_iRecurse = 0;
#endif
        } else {

            // until then, try to come forward.
            Tray_HandleFullScreenApp(FALSE, NULL);
            if (hwndForeground == v_hwndDesktop) {
                SetFocus(g_ts.hwndStart);
                if (GetFocus() != g_ts.hwndStart)
                    return;
            }

            SwitchToThisWindow(pfc->hwndMain, TRUE);
            SetForegroundWindow(v_hwndTray);
            Sleep(20); // give some time for other async activation messages to get posted
            PostMessage(v_hwndTray, TM_ACTASTASKSW, 0, 0);
        }

    }
}


void RebuildMenu(WORD wID)
{
    HMENU hmenuSub;
    hmenuSub = Menu_FindSubMenuByFirstID(g_ts.hmenuStart, wID);
    InvalidateRebuildMenu(wID, hmenuSub);
}

void Tray_SetFSMenuFilter(UINT fFilter)
{
    if (fFilter != g_CabState.fMenuEnumFilter) {
        g_CabState.fMenuEnumFilter = fFilter;
        RebuildMenu(IDM_PROGRAMS);
    }
}

//----------------------------------------------------------------------------
void Tray_OnWinIniChange(PFileCabinet pfc, HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    // Reset the programs menu.
    // REVIEW IANEL - We should only need to listen to the SPI_SETNONCLIENT stuff
    // but deskcpl doesn't send one.
    if (wParam == SPI_SETNONCLIENTMETRICS || (!wParam && (!lParam || (lstrcmpi((LPTSTR)lParam, c_szMetrics) == 0))))
    {
#ifdef DEBUG
        if (wParam == SPI_SETNONCLIENTMETRICS)
            DebugMsg(DM_TRACE, TEXT("c.t_owic: Non-client metrics (probably) changed."));
        else
            DebugMsg(DM_TRACE, TEXT("c.t_owic: Window metrics changed."));
#endif

        Cabinet_InitGlobalMetrics(wParam, (LPTSTR)lParam);
#ifdef DELAYWININICHANGE
        SetTimer(pfc->hwndMain, IDT_DELAYWININICHANGE, 2*1000, NULL);
#else
        Tray_OnDelayWinIniChange(pfc);
#endif

    }

    // Handle old extensions.
    if (!lParam || (lParam && (lstrcmpi((LPTSTR)lParam, c_szExtensions) == 0)))
    {
        DebugMsg(DM_TRACE, TEXT("t_owic: Extensions section change."));
        CheckWinIniForAssocs();
    }

    // Get the printer thread to re-do the icon.
    if (g_ts.htPrinterPoll && g_ts.idPrinterPoll)
    {
        PostThreadMessage(g_ts.idPrinterPoll, WM_USER+SHCNE_UPDATEITEM, 0, 0);
    }
}


//---------------------------------------------------------------------------
HWND HotkeyList_HotkeyInUse(WORD wHK)
{
    HWND hwnd;
    LRESULT lrHKInUse = 0;
    int nMod;
    WORD wHKNew;
#ifdef DEBUG
    TCHAR sz[MAX_PATH];
#endif

    // Map the modifiers back.
    nMod = 0;
    if (HIBYTE(wHK) & MOD_SHIFT)
        nMod |= HOTKEYF_SHIFT;
    if (HIBYTE(wHK) & MOD_CONTROL)
        nMod |= HOTKEYF_CONTROL;
    if (HIBYTE(wHK) & MOD_ALT)
        nMod |= HOTKEYF_ALT;

    wHKNew = (WORD)((nMod*256)+LOBYTE(wHK));

    DebugMsg(DM_IANELHK, TEXT("c.hkl_hiu: Checking for %x"), wHKNew);
    hwnd = GetWindow(GetDesktopWindow(), GW_CHILD);
    while (hwnd)
    {
        SendMessageTimeout(hwnd, WM_GETHOTKEY, 0, 0, SMTO_ABORTIFHUNG|SMTO_BLOCK, 3000, &lrHKInUse);
        if (wHKNew == (WORD)lrHKInUse)
        {
#ifdef DEBUG
            GetWindowText(hwnd, sz, ARRAYSIZE(sz));
            DebugMsg(DM_IANELHK, TEXT("c.hkl_hiu: %s (%x) is using %x"), sz, hwnd, lrHKInUse);
#endif
            return hwnd;
        }
#ifdef DEBUG
        else if (lrHKInUse)
        {
            GetWindowText(hwnd, sz, ARRAYSIZE(sz));
            DebugMsg(DM_IANELHK, TEXT("c.hkl_hiu: %s (%x) is using %x"), sz, hwnd, lrHKInUse);
        }
        // else
        // {
        //     GetWindowText(hwnd, sz, ARRAYSIZE(sz));
        //     DebugMsg(DM_IANELHK, "c.hkl_hiu: %s (%x) has no kotkey assigned.", sz, hwnd);
        // }
#endif
        hwnd = GetWindow(hwnd, GW_HWNDNEXT);
    }
    return NULL;
}

//---------------------------------------------------------------------------
void HotkeyList_HandleHotkey(PFileCabinet pfc, int nID)
{
    PHOTKEYITEM phki;
    BOOL fRes;
    HWND hwnd;

    DebugMsg(DM_TRACE, TEXT("c.hkl_hh: Handling hotkey (%d)."), nID);

    // Find it in the list.
    Assert(g_ts.hdsaHKI);
    phki = DSA_GetItemPtr(g_ts.hdsaHKI, nID);
    if (phki && phki->wGHotkey)
    {
        DebugMsg(DM_TRACE, TEXT("c.hkl_hh: Hotkey listed."));

        // Are global hotkeys enabled?
        if (!g_ts.fGlobalHotkeyDisable)
        {
            // Yep.
            hwnd = HotkeyList_HotkeyInUse(phki->wGHotkey);
            // Make sure this hotkey isn't already in use by someone.
            if (hwnd)
            {
                DebugMsg(DM_TRACE, TEXT("c.hkl_hh: Hotkey is already in use."));
                // Activate it.
                SwitchToThisWindow(GetLastActivePopup(hwnd), TRUE);
            }
            else
            {
                DECLAREWAITCURSOR;
                // Exec the item.
                SetWaitCursor();
                DebugMsg(DM_TRACE, TEXT("c.hkl_hh: Hotkey is not in use, execing item."));
                Assert(phki->pidlFolder && phki->pidlItem);
                    fRes = _ExecItemByPidls(pfc->hwndMain, phki->pidlFolder, phki->pidlItem);
                ResetWaitCursor();
#ifdef DEBUG
                if (!fRes)
                {
                        DebugMsg(DM_ERROR, TEXT("c.hkl_hh: Can't exec command."));
                }
#endif
            }
        }
        else
        {
            DebugMsg(DM_ERROR, TEXT("c.hkl_hh: Global hotkeys have been disabled."));
        }
    }
    else
    {
        DebugMsg(DM_ERROR, TEXT("c.hkl_hh: Hotkey not listed."));
    }
}

//---------------------------------------------------------------------------
LRESULT Tray_UnregisterHotkey(HWND hwnd, int i)
{
    DebugMsg(DM_TRACE, TEXT("c.t_uh: Unregistering hotkey (%d)."), i);

    if (!UnregisterHotKey(hwnd, i))
    {
        DebugMsg(DM_ERROR, TEXT("c.t_rh: Unable to unregister hotkey %d."), i);
    }
    return TRUE;
}

//---------------------------------------------------------------------------
// Add hotkey to the shell's list of global hotkeys.
LRESULT Tray_ShortcutRegisterHotkey(HWND hwnd, WORD wHotkey, ATOM atom)
{
    int i;
    LPITEMIDLIST pidl;
        TCHAR szPath[MAX_PATH];
        Assert(atom);

        if (GlobalGetAtomName(atom, szPath, MAX_PATH))
        {
                DebugMsg(DM_TRACE, TEXT("c.t_srh: Hotkey %d for %s"), wHotkey, szPath);

                pidl = ILCreateFromPath(szPath);
                if (pidl)
                {
                        i = HotkeyList_AddCached(MapHotkeyToGlobalHotkey(wHotkey), pidl);
                        if (i != -1)
                        {
                                Tray_RegisterHotkey(v_hwndTray, i);
                        }
                }
                return TRUE;
        }
        else
        {
                return FALSE;
        }
}

//---------------------------------------------------------------------------
// Remove hotkey from shell's list.
LRESULT Tray_ShortcutUnregisterHotkey(HWND hwnd, WORD wHotkey)
{
    int i;

    // DebugMsg(DM_TRACE, "c.t_suh: Hotkey %d", wHotkey);

    i = HotkeyList_Remove(wHotkey);
    if (i == -1)
        i = HotkeyList_RemoveCached(MapHotkeyToGlobalHotkey(wHotkey));

    if (i != -1)
        Tray_UnregisterHotkey(hwnd, i);

    return TRUE;
}

//---------------------------------------------------------------------------
LRESULT Tray_RegisterHotkey(HWND hwnd, int i)
{
    PHOTKEYITEM phki;
    WORD wGHotkey;
    int iCached;

    DebugMsg(DM_TRACE, TEXT("c.t_rh: Registering hotkey (%d)."), i);

    phki = DSA_GetItemPtr(g_ts.hdsaHKI, i);
    Assert(phki);
    if (phki)
    {
        wGHotkey = phki->wGHotkey;
        if (wGHotkey)
        {
            // Is the hotkey available?
            if (RegisterHotKey(hwnd, i, HIBYTE(wGHotkey), LOBYTE(wGHotkey)))
            {
                // Yes.
                return TRUE;
            }
            else
            {
                // Delete any cached items that might be using this
                // hotkey.
                iCached = HotkeyList_RemoveCached(wGHotkey);
                Assert(iCached != i);
                if (iCached != -1)
                {
                    // Free up the hotkey for us.
                    Tray_UnregisterHotkey(hwnd, iCached);
                    // Yep, nuked the cached item. Try again.
                    if (RegisterHotKey(hwnd, i, HIBYTE(wGHotkey), LOBYTE(wGHotkey)))
                    {
                        return TRUE;
                    }
                }
            }

            // Can't set hotkey for this item.
            DebugMsg(DM_ERROR, TEXT("c.t_rh: Unable to register hotkey %d."), i);
            // Null out this item.
            phki->wGHotkey = 0;
            phki->pidlFolder = NULL;
            phki->pidlItem = NULL;
        }
        else
        {
            DebugMsg(DM_ERROR, TEXT("c.t_rh: Hotkey item is invalid."));
        }
    }
    return FALSE;
}

void AppBarGetTaskBarPos(PTRAYAPPBARDATA ptabd)
{
    LPRECT lprc;

    lprc = (LPRECT)SHLockShared(ptabd->hSharedRect, ptabd->dwProcId);
    if (lprc)
    {
        *lprc = g_ts.arStuckRects[g_ts.uStuckPlace];
        SHUnlockShared(lprc);
    }
}

void NukeAppBar(int i)
{
    Free(DPA_GetPtr(g_ts.hdpaAppBars, i));
    DPA_DeletePtr(g_ts.hdpaAppBars, i);
}

void AppBarRemove(PTRAYAPPBARDATA ptabd)
{
    if (g_ts.hdpaAppBars) {
        int i = DPA_GetPtrCount(g_ts.hdpaAppBars);

        while (i--) {
            PAPPBAR pab = (PAPPBAR)DPA_GetPtr(g_ts.hdpaAppBars, i);
            if (ptabd->abd.hWnd == pab->hwnd) {
                NukeAppBar(i);
            }
        }
    }
}

PAPPBAR FindAppBar(HWND hwnd)
{
    int i;
    if (g_ts.hdpaAppBars)  {

        i = DPA_GetPtrCount(g_ts.hdpaAppBars);

        while (i--) {
            PAPPBAR pab = (PAPPBAR)DPA_GetPtr(g_ts.hdpaAppBars, i);
            if (hwnd == pab->hwnd) {
                return pab;
            }
        }
    }

    return NULL;
}

void AppBarNotifyAll(UINT uMsg, HWND hwndExclude, LPARAM lParam)
{
    if (g_ts.hdpaAppBars) {
        int i = DPA_GetPtrCount(g_ts.hdpaAppBars);

        while (i--) {
            PAPPBAR pab = (PAPPBAR)DPA_GetPtr(g_ts.hdpaAppBars, i);
            if (hwndExclude != pab->hwnd) {
                if (!IsWindow(pab->hwnd))
                    NukeAppBar(i);
                SendMessage(pab->hwnd, pab->uCallbackMessage, uMsg, lParam);
            }
        }
    }
}

BOOL AppBarNew(PTRAYAPPBARDATA ptabd)
{
    PAPPBAR pab;
    if (!g_ts.hdpaAppBars) {
        g_ts.hdpaAppBars = DPA_Create(4);
        if (!g_ts.hdpaAppBars)
            return FALSE;

    } else if (FindAppBar(ptabd->abd.hWnd)) {

        // already have this hwnd
        return FALSE;
    }

    pab = (PAPPBAR)LocalAlloc(LPTR, SIZEOF(APPBAR));
    if (!pab)
        return FALSE;

    pab->hwnd = ptabd->abd.hWnd;
    pab->uCallbackMessage = ptabd->abd.uCallbackMessage;
    pab->uEdge = (UINT)-1;

    return ( DPA_InsertPtr(g_ts.hdpaAppBars, 0x7fff, pab) != -1);
}


UINT AppBarGetState()
{
    return (g_ts.uAutoHide ?  ABS_AUTOHIDE    : 0) |
        (g_ts.fAlwaysOnTop ?  ABS_ALWAYSONTOP : 0);
}

BOOL AppBarOutsideOf(PAPPBAR pabReq, PAPPBAR pab)
{
    if (pabReq->uEdge == pab->uEdge) {
        switch (pab->uEdge) {
        case ABE_RIGHT:
            return (pab->rc.right >= pabReq->rc.right);

        case ABE_BOTTOM:
            return (pab->rc.bottom >= pabReq->rc.bottom);

        case ABE_TOP:
            return (pab->rc.top <= pabReq->rc.top);

        case ABE_LEFT:
            return (pab->rc.left <= pabReq->rc.left);
        }
    }
    return FALSE;
}

void AppBarQueryPos(PTRAYAPPBARDATA ptabd)
{
    int i;
    PAPPBAR pabReq = FindAppBar(ptabd->abd.hWnd);

    if (pabReq) {
        LPRECT lprc;

        lprc = (LPRECT)SHLockShared(ptabd->hSharedRect, ptabd->dwProcId);
        if (lprc)
        {
            *lprc = ptabd->abd.rc;

            // always subtract off the tray
            if (!g_ts.uAutoHide) {
                APPBAR ab;
                ab.uEdge = g_ts.uStuckPlace;
                ab.rc = g_ts.arStuckRects[g_ts.uStuckPlace];
                AppBarSubtractRect(&ab, lprc);
            }

            i = DPA_GetPtrCount(g_ts.hdpaAppBars);

            while (i--) {
                PAPPBAR pab = (PAPPBAR)DPA_GetPtr(g_ts.hdpaAppBars, i);

                // give top and bottom preference
                // ||
                // if we're not changing edges, subtract anything currently on the outside of us
                // ||
                // if we are changing sides, subtract off everything on the new side.
                if (
                    ((pabReq->hwnd != pab->hwnd) && STUCK_HORIZONTAL(pab->uEdge) && !STUCK_HORIZONTAL(ptabd->abd.uEdge))
                    ||
                    ((pabReq->hwnd != pab->hwnd) && (pabReq->uEdge == ptabd->abd.uEdge) && AppBarOutsideOf(pabReq, pab))
                    ||
                    ((pabReq->hwnd != pab->hwnd) && (pabReq->uEdge != ptabd->abd.uEdge) && (pab->uEdge == ptabd->abd.uEdge))

                    )
                {
                    AppBarSubtractRect(pab, lprc);
                }
            }
            SHUnlockShared(lprc);
        }
    }
}

void AppBarSetPos(PTRAYAPPBARDATA ptabd)
{
    PAPPBAR pab = FindAppBar(ptabd->abd.hWnd);

    if (pab) {
        LPRECT lprc;

        AppBarQueryPos(ptabd);

        lprc = (LPRECT)SHLockShared(ptabd->hSharedRect, ptabd->dwProcId);
        if (lprc)
        {
            if (!EqualRect(&pab->rc, lprc)) {
                pab->rc = *lprc;
                pab->uEdge = ptabd->abd.uEdge;
                PostMessage(v_hwndTray, TM_RELAYPOSCHANGED, (WPARAM)ptabd->abd.hWnd, 0);

                StuckSizeChange();
                SendMessage(v_hwndDesktop, DTM_SIZEDESKTOP, 0, 0);
            }
            SHUnlockShared(lprc);
        }
    }
}



HWND AppBarGetAutoHideBar(UINT uEdge)
{
    if (uEdge >= ABE_MAX)
        return FALSE;
    else {
        HWND hwndAutoHide = g_hwndAutoHide[uEdge];
        if (!IsWindow(hwndAutoHide)) {
            g_hwndAutoHide[uEdge] = NULL;
        }
        return g_hwndAutoHide[uEdge];
    }
}

BOOL IAppBarSetAutoHideBar(HWND hwnd, BOOL fSet, UINT uEdge)
{
    HWND hwndAutoHide = g_hwndAutoHide[uEdge];
    if (!IsWindow(hwndAutoHide)) {
        g_hwndAutoHide[uEdge] = NULL;
    }

    // register
    if (fSet) {
        if (!g_hwndAutoHide[uEdge]) {
            g_hwndAutoHide[uEdge] = hwnd;
        }

        return g_hwndAutoHide[uEdge] == hwnd;
    } else {
        // unregister
        if (g_hwndAutoHide[uEdge] == hwnd) {
            g_hwndAutoHide[uEdge] = NULL;
        }
        return TRUE;
    }
}

BOOL AppBarSetAutoHideBar(PTRAYAPPBARDATA ptabd)
{
    UINT uEdge = ptabd->abd.uEdge;
    if (uEdge >= ABE_MAX)
        return FALSE;
    else {
        return IAppBarSetAutoHideBar(ptabd->abd.hWnd, ptabd->abd.lParam, uEdge);
    }
}

void IAppBarActivationChange(HWND hWnd, UINT uEdge)
{
    HWND hwndAutoHide = AppBarGetAutoHideBar(uEdge);
    if (hwndAutoHide &&
        (hwndAutoHide != hWnd)) {

        // this needs to be done on a postmessage so that USER will have a chance to
        PostMessage(v_hwndTray, TM_BRINGTOTOP, (WPARAM)hwndAutoHide, uEdge);
    }
}

void AppBarActivationChange(PTRAYAPPBARDATA ptabd)
{
    PAPPBAR pab = FindAppBar(ptabd->abd.hWnd);

    if (pab) {
        IAppBarActivationChange(ptabd->abd.hWnd, pab->uEdge);
    }
}

UINT TrayAppBarMessage(PFileCabinet pfc, PCOPYDATASTRUCT pcds)
{
    PTRAYAPPBARDATA ptabd = (PTRAYAPPBARDATA)pcds->lpData;

    Assert(pcds->cbData == SIZEOF(TRAYAPPBARDATA));
    Assert(ptabd->abd.cbSize == SIZEOF(APPBARDATA));

    switch (ptabd->dwMessage) {
    case ABM_NEW:
        return AppBarNew(ptabd);

    case ABM_REMOVE:
        AppBarRemove(ptabd);
        AppBarNotifyAll(ABN_POSCHANGED, NULL, 0);
        StuckSizeChange();
        break;

    case ABM_QUERYPOS:
        AppBarQueryPos(ptabd);
        break;

    case ABM_SETPOS:
        AppBarSetPos(ptabd);
        break;

    case ABM_GETSTATE:
        return AppBarGetState();

    case ABM_GETTASKBARPOS:
        AppBarGetTaskBarPos(ptabd);
        break;

    case ABM_WINDOWPOSCHANGED:
    case ABM_ACTIVATE:
        AppBarActivationChange(ptabd);
        break;

    case ABM_GETAUTOHIDEBAR:
        return (UINT)AppBarGetAutoHideBar(ptabd->abd.uEdge);

    case ABM_SETAUTOHIDEBAR:
        return AppBarSetAutoHideBar(ptabd);

    default:
        return FALSE;
    }

    return TRUE;

}

UINT TrayLoadInProc(PFileCabinet pfc, PCOPYDATASTRUCT pcds)
{
    LPUNKNOWN punk;
    HRESULT hres = SHCoCreateInstance(NULL, (CLSID *)pcds->lpData, NULL, &IID_IUnknown, &punk);
    if (SUCCEEDED(hres))
    {
        punk->lpVtbl->Release(punk);
    }
    return (UINT)hres;
}

//---------------------------------------------------------------------------
// Allow the trays global hotkeys to be disabled for a while.
LRESULT Tray_SetHotkeyEnable(HWND hWnd, BOOL fEnable)
{
    g_ts.fGlobalHotkeyDisable = (fEnable ? FALSE : TRUE);
    return TRUE;
}

BOOL IsPosInHwnd(LPARAM lParam, HWND hwnd)
{
    RECT r1;
    POINT pt;

    pt.x = LOWORD(lParam);
    pt.y = HIWORD(lParam);
    GetWindowRect(hwnd, &r1);
    return PtInRect(&r1, pt);
}

void Tray_HandleWindowPosChanging(PFileCabinet pfc, LPWINDOWPOS lpwp)
{
    //DebugMsg(DM_TRACE, "Tray -- WM_WINDOWPOSCHANGING %x, %d %d %d %d",
    //lpwp->flags, lpwp->x, lpwp->y, lpwp->cx, lpwp->cy);
    //the user could have hit esc, in which case, what we had
    // in our g_ts.uMoveStuckPlace is bogus
    Tray_WhichStuckRect(lpwp->x, lpwp->y, lpwp->x + lpwp->cx, lpwp->y + lpwp->cy);
    if (g_ts.uMoveStuckPlace != (UINT)-1) {
        if (pfc->hwndView)
            Tray_HandleSizing(pfc, 0, &g_ts.arStuckRects[g_ts.uMoveStuckPlace], g_ts.uMoveStuckPlace);
        Tray_DoneMoving(lpwp);
        g_ts.uMoveStuckPlace = (UINT)-1;
    } else if (!g_ts.fSysSizing && !g_ts.fSelfSizing) {
        // if someone's trying to force us to move
        lpwp->flags |= (SWP_NOMOVE | SWP_NOSIZE);
    }
}


//---------------------------------------------------------------------------
LRESULT CALLBACK TrayWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    RECT r1;
    POINT pt;
    DWORD dw;
    PFileCabinet pfc = g_pfcTray;

    switch (uMsg) {
    case WMTRAY_PROGCHANGE:
    case WMTRAY_RECCHANGE:
    case WMTRAY_FAVCHANGE:
    case WMTRAY_PRINTCHANGE:
    case WMTRAY_DESKTOPCHANGE:
    case WMTRAY_COMMONPROGCHANGE:
    case WMTRAY_COMMONFAVCHANGE:
        {
            LPSHChangeNotificationLock pshcnl;
            LPITEMIDLIST *ppidl;
            LONG lEvent;
            LRESULT lResult = 0;

            pshcnl = SHChangeNotification_Lock((HANDLE)wParam, (DWORD)lParam, &ppidl, &lEvent);
            if (pshcnl)
            {
                lResult = Tray_HandleFSNotify(uMsg, pfc, ppidl, lEvent);
                SHChangeNotification_Unlock(pshcnl);
            }

            return lResult;
        }

    case WMTRAY_PRINTICONNOTIFY:
        PrintNotify_IconNotify(hWnd, (UINT)lParam);
        break;

    case WMTRAY_REGISTERHOTKEY:
        return Tray_RegisterHotkey(hWnd, wParam);

    case WMTRAY_UNREGISTERHOTKEY:
        return Tray_UnregisterHotkey(hWnd, wParam);

    case WMTRAY_SCREGISTERHOTKEY:
        return Tray_ShortcutRegisterHotkey(hWnd, (WORD)wParam, (ATOM)lParam);

    case WMTRAY_SCUNREGISTERHOTKEY:
        return Tray_ShortcutUnregisterHotkey(hWnd, (WORD)wParam);

    case WMTRAY_SETHOTKEYENABLE:
        return Tray_SetHotkeyEnable(hWnd, (BOOL)wParam);

    case WM_MENUCHAR:
        return FileMenu_HandleMenuChar((HMENU)lParam, (TCHAR)LOWORD(wParam));

    case WM_COPYDATA:
        // Check for NULL it can happen if user runs out of selectors or memory...
        if (lParam)
        {
            switch (((PCOPYDATASTRUCT)lParam)->dwData) {
            case TCDM_NOTIFY:
                dw = TrayNotify(g_ts.hwndNotify, (HWND)wParam, (PCOPYDATASTRUCT)lParam);
                Tray_SizeWindows();
                return(dw);

            case TCDM_APPBAR:
                return TrayAppBarMessage(pfc, (PCOPYDATASTRUCT)lParam);

            case TCDM_LOADINPROC:
                return TrayLoadInProc(pfc, (PCOPYDATASTRUCT)lParam);
            }
        }
        return FALSE;

        case WM_NCLBUTTONDBLCLK:
            if (IsPosInHwnd(lParam, g_ts.hwndNotify)) {
                TrayCommand(pfc, IDM_SETTIME);
            }
            break;

    case WM_NCMOUSEMOVE:
        if (IsPosInHwnd(lParam, g_ts.hwndNotify)) {
            MSG msg;
            msg.lParam = lParam;
            msg.wParam = wParam;
            msg.message = WM_NCMOUSEMOVE;
            msg.hwnd = hWnd;
            SendMessage(g_ts.hwndTrayTips, TTM_RELAYEVENT, 0, (LPARAM)(LPMSG)&msg);
        }
        goto DoDefault;

    case WM_CREATE:
        Tray_OnCreate(hWnd, (LPCREATESTRUCT)lParam);
        break;

    case WM_DESTROY:
        return Tray_HandleDestroy(pfc);

    case WM_ENDSESSION:
        // save our settings if someone else is shutting things down
        if (wParam) {
            DebugMsg(DM_TRACE, TEXT("Tray WM_ENDSESSION"));
            _SaveTrayAndDesktop();
            ShowWindow(v_hwndTray, SW_HIDE);
            ShowWindow(v_hwndDesktop, SW_HIDE);
            RegisterShellHook(pfc->hwndView, FALSE);
        }
        break;

    //
    // always repaint the screen when the machine wakes up from
    // a suspend.  NOTE we dont  need this for a standby suspend.
    //
    // a critical resume does not generate a WM_POWERBROADCAST
    // to windows for some reason, but it does generate a old
    // WM_POWER message.
    //
    case WM_POWER:
        if (wParam != PWR_CRITICALRESUME)
            goto DoDefault;
#ifndef WINNT       // BUGBUG - Fix this when NT gets APM
        wParam = PBT_APMRESUMECRITICAL;
#endif
        // fall through to WM_POWERBROADCAST

    case WM_POWERBROADCAST:
#ifndef WINNT       // BUGBUG - Fix this when NT gets APM
        switch (wParam)
        {
            case PBT_APMRESUMECRITICAL:
            case PBT_APMRESUMESUSPEND:
                ChangeDisplaySettings(NULL, CDS_RESET);
                break;
        }
#endif
        goto DoDefault;

    case WM_DEVICECHANGE:
            if (wParam == DBT_CONFIGCHANGED)
            {
                UpdateDockingFlags();
                g_ts.DockedState = GetDockedState();

                if (IsEjectAllowed())
                {
                    HandleDisplayChange(0,0,TRUE);
                    // Rebuild the entire start menu next chance we get.
                    g_ts.fFavInvalid = TRUE;
                    SetTimer(v_hwndTray, IDT_FAVOURITE, 2*1000, NULL);
                }
            }
            else if (wParam == DBT_QUERYCHANGECONFIG)
            {
                if (IsEjectAllowed() && !IsDisplayChangeSafe())
                {
                    if (ShellMessageBox(hinstCabinet, hWnd,
                        MAKEINTRESOURCE(IDS_DISPLAY_WARN),
                        MAKEINTRESOURCE(IDS_WINDOWS),
                        MB_ICONEXCLAMATION|MB_YESNO|MB_DEFBUTTON2) == IDNO)
                    {
                        return BROADCAST_QUERY_DENY;
                    }
                }

                //
                // drop down to 640x480 (or the lowest res for all configs)
                // before the config change.  this makes sure we dont screw
                // up some laptop display panels.
                //
                dw = GetMinDisplayRes();
                HandleDisplayChange(LOWORD(dw), HIWORD(dw), FALSE);
            }
            else if (wParam == DBT_MONITORCHANGE)
            {
                //
                // handle monitor change
                //
                HandleDisplayChange(LOWORD(lParam),HIWORD(lParam),TRUE);
            }
            else if (wParam == DBT_CONFIGCHANGECANCELED)
            {
                //
                // if the config change was canceled go back
                //
                HandleDisplayChange(0,0,FALSE);
            }
        goto DoDefault;

        case WM_NOTIFY:
            switch (((NMHDR *)lParam)->code) {

                case SEN_DDEEXECUTE:
                    return (DDEHandleViewFolderNotify(pfc, (LPNMVIEWFOLDER)lParam));

                case NM_STARTWAIT:
                case NM_ENDWAIT:
                    Cabinet_OnWaitCursorNotify(pfc, (NMHDR *)lParam);
                    SendMessage(v_hwndDesktop, uMsg, wParam, lParam); // forward it along
                    break;

#define lpttt ((LPTOOLTIPTEXT)lParam)
            case TTN_NEEDTEXT:

                GetDateFormat(LOCALE_USER_DEFAULT, DATE_LONGDATE, NULL, NULL, lpttt->szText, ARRAYSIZE(lpttt->szText));
                    return TRUE;


            case TTN_SHOW:
                SetWindowPos(g_ts.hwndTrayTips,
                             HWND_TOP,
                             0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                    break;

                }
            break;

    case WM_CLOSE:
        DoExitWindows(hWnd);
        return 0;

        case WM_NCHITTEST:
            GetClientRect(hWnd, &r1);
            MapWindowPoints(hWnd, NULL, (LPPOINT)&r1, 2);

            pt.x = LOWORD(lParam);
            pt.y = HIWORD(lParam);

            TraySetUnhideTimer(pt.x, pt.y);

            // allow dragging if mouse is in client area of v_hwndTray
            if (PtInRect(&r1, pt)) {
                return HTCAPTION;
            }
            else {
                switch (g_ts.uStuckPlace) {
                    case STICK_LEFT:
                        dw = ((pt.x > r1.right) ? HTRIGHT : HTBORDER);
                        break;
                    case STICK_TOP:
                        dw = ((pt.y > r1.bottom) ? HTBOTTOM : HTBORDER);
                        break;
                    case STICK_RIGHT:
                        dw = ((pt.x < r1.left) ? HTLEFT : HTBORDER);
                        break;
                    case STICK_BOTTOM:
                    default:
                        dw = ((pt.y < r1.top) ? HTTOP : HTBORDER);
                        break;
                }
                return dw;
            }
            break;

        case WM_WINDOWPOSCHANGING:
            Tray_HandleWindowPosChanging(pfc, (LPWINDOWPOS)lParam);
            break;

        // BUGBUG this needs to be changed with WM_MOVING
        // trap the start and end of a sizing/moving operation and
        // make sure that we don't actually do anything until done.
        // this avoids some crazy resizing with full window dragging.
        case WM_ENTERSIZEMOVE:
            //DebugMsg(DM_TRACE, "Tray -- WM_ENTERSIZEMOVE");
            g_ts.fSysSizing = TRUE;
            break;

        case WM_EXITSIZEMOVE:
            //DebugMsg(DM_TRACE, "Tray -- WM_EXITSIZEMOVE");
            g_ts.fSysSizing = FALSE;
            PostMessage(hWnd, WM_SIZE, 0, 0L);
            break;

        case WM_MOVING:
            //DebugMsg(DM_TRACE, "Tray -- WM_MOVING");
            Tray_HandleMoving((LPRECT)lParam);
            Tray_HandleSizing(pfc, wParam, (LPRECT)lParam, g_ts.uMoveStuckPlace);
            break;

        case WM_ENTERMENULOOP:
            // DebugMsg(DM_TRACE, "c.twp: Enter menu loop.");
            Tray_HandleEnterMenuLoop();
            Cabinet_ForwardViewMsg(pfc, uMsg, wParam, lParam);
            break;

        case WM_EXITMENULOOP:
            // DebugMsg(DM_TRACE, "c.twp: Exit menu loop.");
            Tray_HandleExitMenuLoop();
            Cabinet_ForwardViewMsg(pfc, uMsg, wParam, lParam);
            break;

        case WM_TIMER:
            Tray_HandleTimer(pfc, wParam);
            break;

        case WM_SIZING:
            //DebugMsg(DM_TRACE, "Tray -- WM_SIZING");
            Tray_HandleSizing(pfc, wParam, (LPRECT)lParam, g_ts.uStuckPlace);
            break;

    case WM_SIZE:
        if (pfc && ((GetWindowLong(hWnd, GWL_STYLE)) & WS_MINIMIZE)) {
            ShowWindow(hWnd, SW_RESTORE);
        }
            if (!g_ts.fSysSizing || g_fDragFullWindows)
            {
                Tray_SizeWindows();
            }

            if (!g_ts.fSysSizing && !g_ts.fSelfSizing) {
                // we get a couple size messages while being created.
                // our window rect means nothing then, so punt out
                if (IsWindowVisible(hWnd)) {
                    StuckSizeChange();
                    AppBarNotifyAll(ABN_POSCHANGED, NULL, 0);
                }
            }
        break;

        case WM_DISPLAYCHANGE:
            Tray_ScreenSizeChange(hWnd);
            break;

        // Don't go to default wnd proc for this one...
        case WM_INPUTLANGCHANGEREQUEST:
            return(LRESULT)0L;

        case WM_GETMINMAXINFO:
            ((MINMAXINFO *)lParam)->ptMinTrackSize.x = g_cxFrame;
            ((MINMAXINFO *)lParam)->ptMinTrackSize.y = g_cyFrame;
            break;

        case WM_WININICHANGE:
            Cabinet_PropagateMessage(hWnd, uMsg, wParam, lParam);
            Tray_OnWinIniChange(pfc, hWnd, wParam, lParam);
            break;

        case WM_TIMECHANGE:
            Cabinet_PropagateMessage(hWnd, uMsg, wParam, lParam);
            break;

        case WM_SYSCOLORCHANGE:
            StartButton_ResetAndSize(pfc);
            Cabinet_PropagateMessage(hWnd, uMsg, wParam, lParam);
            break;

        case WM_MEASUREITEM:
            return Tray_HandleMeasureItem(hWnd, (LPMEASUREITEMSTRUCT)lParam);
        case WM_DRAWITEM:
            return Tray_HandleDrawItem(hWnd, (LPDRAWITEMSTRUCT)lParam);
        case WM_INITMENUPOPUP:
            // DebugMsg(DM_TRACE, "c.twp: Init menu popup.");
            // bugbug, does Tray_HandleInitMenuPopup return anything other than 1?
            if (!Tray_HandleInitMenuPopup((HMENU)wParam))
                Cabinet_ForwardViewMsg(pfc, uMsg, wParam, lParam);
            break;
        case WM_SETCURSOR:
            if (pfc->iWaitCount) {
                //DebugMsg(DM_TRACE,"########### SET WAIT CURSOR WM_SETCURSOR %d", pfc->iWaitCount);
                SetCursor(LoadCursor(NULL, IDC_APPSTARTING));
                return TRUE;
            } else
                goto DoDefault;

        case WM_SETFOCUS:
            if (pfc && pfc->hwndView)
                SetFocus(pfc->hwndView);
            break;

        // this keeps our window from comming to the front on button down
        // instead, we activate the window on the up click
        case WM_MOUSEACTIVATE:
            if (LOWORD(lParam) == HTCLIENT)
                return MA_NOACTIVATE;
            else
                goto DoDefault;

        case WM_SYSCHAR:
            if (wParam == TEXT(' ')) {
                HMENU hmenu;
                int idCmd;

                SetWindowStyleBit(hWnd, WS_SYSMENU, WS_SYSMENU);
                hmenu = GetSystemMenu(hWnd, FALSE);
                if (hmenu) {
                    EnableMenuItem(hmenu, SC_RESTORE, MFS_GRAYED | MF_BYCOMMAND);
                    EnableMenuItem(hmenu, SC_MAXIMIZE, MFS_GRAYED | MF_BYCOMMAND);
                    EnableMenuItem(hmenu, SC_MINIMIZE, MFS_GRAYED | MF_BYCOMMAND);
                    idCmd = Tray_TrackMenu(pfc, hmenu, FALSE);
                    if (idCmd)
                        SendMessage(v_hwndTray, WM_SYSCOMMAND, idCmd, 0L);
                }
                SetWindowStyleBit(hWnd, WS_SYSMENU, 0L);
            }
            break;

        case WM_SYSCOMMAND:

            // if we are sizing, make the full screen accessible
            switch (wParam & 0xFFF0) {
            case SC_CLOSE:
                DoExitWindows(hWnd);
                break;

            default:
                goto DoDefault;
            }
            break;

        case TM_ACTASTASKSW:
            Tray_ActAsSwitcher(pfc);
            break;

        case TM_RELAYPOSCHANGED:
            AppBarNotifyAll(ABN_POSCHANGED, (HWND)wParam, lParam);
            break;

    case TM_BRINGTOTOP:

        SetWindowPos((HWND)wParam, HWND_TOP,
                     0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
        break;

    case TM_INVALIDATEREBUILDMENU:
        InvalidateRebuildMenu((WORD)wParam, (HMENU)lParam);
        break;

    case WM_NCRBUTTONUP:
        wParam = (WPARAM)pfc->hwndView;
    case WM_CONTEXTMENU:
        DebugMsg(DM_TRACE, TEXT("TRAY GOT CONTEXT MENU!"));

        if (SHRestricted(REST_NOTRAYCONTEXTMENU)) {
            break;
        }

        if (((HWND)wParam) == g_ts.hwndStart)
        {
#if 0
            /* The following test is bogus, as it stops the start context menu being displayed if
            /  the find option is disabled, therefore we no longer perform it, we just display the menu
            /  which filters out the options anyway. daviddv 27mar95 */

            // put up the context menu for the start menu folder.
            if (!SHRestricted(REST_NOFIND) && !SHRestricted(REST_NOSETTASKBAR))
#endif
                StartMenuFolder_ContextMenu(pfc, lParam);
        } else {
            BOOL fIncludeTime;

            // if it was done by keyboard or if click was inthe clock, include
            // the time
            fIncludeTime = (lParam == (DWORD)-1) || IsPosInHwnd(lParam, g_ts.hwndNotify);
            Tray_ContextMenu(pfc, lParam, fIncludeTime);
        }
        break;

    case WM_HOTKEY:
        HotkeyList_HandleHotkey(pfc, (WORD)wParam);
        break;

    case WM_COMMAND:
        TrayCommand(pfc, GET_WM_COMMAND_ID(wParam, lParam));
        break;

    case WM_WINDOWPOSCHANGED:
        IAppBarActivationChange(hWnd, g_ts.uStuckPlace);
        goto DoDefault;

    case WM_ACTIVATE:
        IAppBarActivationChange(hWnd, g_ts.uStuckPlace);
        if (wParam != WA_INACTIVE)
            TrayUnhide();
        // Fall through

    default:
DoDefault:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

//----------------------------------------------------------------------------
// Just like shells SHRestricted() only this put up a message if the restricion
// is in effect.
BOOL _Restricted(HWND hwnd, RESTRICTIONS rest)
{
    if (SHRestricted(rest))
    {
        ShellMessageBox(hinstCabinet, hwnd, MAKEINTRESOURCE(IDS_RESTRICTIONS),
            MAKEINTRESOURCE(IDS_RESTRICTIONSTITLE), MB_OK|MB_ICONSTOP);
        return TRUE;
    }
    return FALSE;
}

//----------------------------------------------------------------------------
void DoExitWindows(HWND hwnd)
{
        if (_Restricted(hwnd, REST_NOCLOSE))
                return;

        SetFocus(hwnd);
        _SaveTrayAndDesktop();
        g_ts.uModalMode = MM_SHUTDOWN;
        ExitWindowsDialog(hwnd);
        g_ts.uModalMode = 0;
        if ((GetKeyState(VK_SHIFT) < 0) && (GetKeyState(VK_CONTROL) < 0) && (GetKeyState(VK_MENU) < 0))
        {
                // User cancelled...
                // The shift key means exit the tray...
                // ??? - Used to destroy all cabinets...
                // PostQuitMessage(0);
                DebugMsg(DM_TRACE, TEXT("c.dew: Posting quit message for %#08x."), GetCurrentThreadId());
                PostMessage(v_hwndDesktop, WM_QUIT, 0, 0);
        }
}

#define MAX_FILE_PROP_PAGES 5  // More than enough
BOOL CALLBACK _TrayAddPropSheetPage(HPROPSHEETPAGE hpage, LPARAM lParam)
{
    PROPSHEETHEADER * ppsh = (PROPSHEETHEADER *)lParam;

    if (ppsh->nPages < MAX_FILE_PROP_PAGES)
    {
        ppsh->phpage[ppsh->nPages++] = hpage;
        return TRUE;
    }

    return FALSE;
}


void RealTrayProperties(HWND hwndParent, PFileCabinet pfc)
{
    HPROPSHEETPAGE ahpage[MAX_FILE_PROP_PAGES];
    TCHAR szPath[MAX_PATH];
    PROPSHEETHEADER psh;

    LoadString(hinstCabinet, IDS_TASKBAR, szPath, ARRAYSIZE(szPath));

    psh.dwSize = SIZEOF(psh);
    psh.dwFlags = PSH_PROPTITLE;
    psh.hInstance = hinstCabinet;
    psh.hwndParent = hwndParent;
    psh.pszCaption = szPath;
    psh.nPages = 0;     // incremented in callback
    psh.nStartPage = 0;
    psh.phpage = ahpage;

    CShellTray_AddViewPropertySheetPages(&pfc->sb, 0L, _TrayAddPropSheetPage, (LPARAM)(LPPROPSHEETHEADER)&psh);

    // Open the property sheet, only if we have some pages.
    if (psh.nPages > 0)
    {
        //g_ts.uModalMode = MM_OTHER;
        PropertySheet(&psh);
        //g_ts.uModalMode = 0;
    }
}



DWORD WINAPI TrayPropertiesThread(LPVOID lpData)
{
    HWND hwnd;
    RECT rc;
    GetWindowRect(g_ts.hwndStart, &rc);
    g_ts.hwndProp = hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, c_szStatic, NULL, 0   ,
        rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hinstCabinet, NULL);

    if (g_ts.hwndProp) {
        // SwitchToThisWindow(hwnd, TRUE);
        // SetForegroundWindow(hwnd);
        RealTrayProperties(hwnd, (PFileCabinet)lpData);
        g_ts.hwndProp = NULL;
        DestroyWindow(hwnd);
    }
    return TRUE;
}

void DoTrayProperties(PFileCabinet pfc)
{
    HANDLE ht;
    DWORD  id;

    if (!_Restricted(pfc->hwndMain, REST_NOSETTASKBAR))
    {
        int i = RUNWAITSECS;
        while (g_ts.hwndProp == ((HWND)-1) &&i--) {
            // we're in the process of coming up. wait
            Sleep(SECOND);
        }

        // failed!  blow it off.
        if (g_ts.hwndProp == (HWND)-1)
            g_ts.hwndProp = NULL;

        if (g_ts.hwndProp)
        {
            // there's a window out there... activate it
            SwitchToThisWindow(GetLastActivePopup(g_ts.hwndProp), TRUE);
        } else {
            g_ts.hwndProp = (HWND)-1;
            ht = CreateThread(NULL, 0, TrayPropertiesThread, (LPVOID)pfc, 0, &id);
            if (ht) {
                CloseHandle(ht);
            } else {
                g_ts.hwndProp = 0;
            }
        }
    }
}




void ShowFolder(HWND hwnd, WPARAM wparam, UINT uFlags)
{
    LPITEMIDLIST pidl = NULL;

        if (_Restricted(hwnd, REST_NOSETFOLDERS))
                return;

    switch(wparam)
    {
        case IDM_CONTROLS:
            pidl = SHCloneSpecialIDList(NULL, CSIDL_CONTROLS, FALSE);
            break;
        case IDM_PRINTERS:
            pidl = SHCloneSpecialIDList(NULL, CSIDL_PRINTERS, FALSE);
            break;

        case IDM_MYCOMPUTER:
            pidl = SHCloneSpecialIDList(NULL, CSIDL_DRIVES, FALSE);
            break;
    }

    if (pidl)
    {
        NEWFOLDERINFO fi;

        fi.hwndCaller = NULL;
        fi.pidl = pidl;
        fi.uFlags = uFlags;
        fi.nShow = SW_SHOWNORMAL;
        fi.dwHotKey = 0;

        Cabinet_OpenFolder(&fi);

        ILFree(pidl);
    }
}

void DoEjectPC()
{
#ifndef WINNT       // BUGBUG - Fix this when NT gets docking capability
    BIOSPARAMS bp;
    DWORD cbOut;
    DECLAREWAITCURSOR;

    SetWaitCursor();

    if (g_ts.hBIOS!=INVALID_HANDLE_VALUE)
        DeviceIoControl(
            g_ts.hBIOS,
            PNPBIOS_SERVICE_SOFTUNDOCK,
            &bp,
            SIZEOF(bp),
            &bp,
            SIZEOF(bp),
            &cbOut,
            NULL);

    ResetWaitCursor();

    return;
#endif
}

BOOL Tray_IsAutoHide()
{
    return g_ts.uAutoHide & AH_ON;
}

DWORD Tray_GetStuckPlace()
{
    return g_ts.uStuckPlace;
}

BOOL CanMinimizeAll(HWND hwndView);

BOOL CanTileWindow(HWND hwnd, LPARAM lParam)
{
    BOOL *pf = (BOOL*)lParam;

    if (IsWindowVisible(hwnd) && !IsIconic(hwnd) && (hwnd != v_hwndTray) && hwnd != v_hwndDesktop) {
        *pf = TRUE;
    }
    return !*pf;
}

BOOL CanTileAnyWindows()
{
    BOOL f = FALSE;
    EnumWindows(CanTileWindow, (LPARAM)&f);
    return f;
}

void Tray_ContextMenu(PFileCabinet pfc, DWORD dwPos, BOOL fIncludeTime)
{
    HMENU hmenu;
    HMENU hmContext;
    TCHAR szMenu[64];
    TCHAR szTemplate[30];
    TCHAR szCommand[30];
    int idCmd;

    if (dwPos == (DWORD)-1) {
        POINT pt = {0,0};
        ClientToScreen(pfc->hwndView, &pt);
        dwPos = MAKELONG(pt.x, pt.y);
    }

    SwitchToThisWindow(pfc->hwndMain, TRUE);
    SetForegroundWindow(pfc->hwndMain);

    hmenu = LoadMenu(hinstCabinet, MAKEINTRESOURCE(MENU_TRAYCONTEXT));
    if (hmenu) {
        hmContext = GetSubMenu(hmenu, 0);
        if (fIncludeTime) {
            SetMenuDefaultItem(hmContext, IDM_SETTIME, MF_BYCOMMAND);
        } else {
            DeleteMenu(hmContext, IDM_SETTIME, MF_BYCOMMAND);
        }

        if (!g_ts.pPositions) {
            DeleteMenu(hmContext, IDM_UNDO, MF_BYCOMMAND);
        } else {
            LoadString(hinstCabinet, IDS_UNDOTEMPLATE, szTemplate, ARRAYSIZE(szTemplate));
            LoadString(hinstCabinet, g_ts.pPositions->idRes, szCommand, ARRAYSIZE(szCommand));
            wsprintf(szMenu, szTemplate, szCommand);
            ModifyMenu(hmContext, IDM_UNDO, MF_BYCOMMAND | MF_STRING, IDM_UNDO, szMenu);
        }

        if (!CanMinimizeAll(pfc->hwndView))
            EnableMenuItem(hmContext, IDM_MINIMIZEALL, MFS_GRAYED | MF_BYCOMMAND);

        if (!CanTileAnyWindows()) {
            EnableMenuItem(hmContext, IDM_CASCADE, MFS_GRAYED | MF_BYCOMMAND);
            EnableMenuItem(hmContext, IDM_HORIZTILE, MFS_GRAYED | MF_BYCOMMAND);
            EnableMenuItem(hmContext, IDM_VERTTILE, MFS_GRAYED | MF_BYCOMMAND);

        }

        SendMessage(g_ts.hwndTrayTips, TTM_ACTIVATE, FALSE, 0L);
        idCmd = TrackPopupMenu(hmContext, TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
                       LOWORD(dwPos), HIWORD(dwPos), 0, v_hwndTray, NULL);
        SendMessage(g_ts.hwndTrayTips, TTM_ACTIVATE, TRUE, 0L);
        DestroyMenu(hmenu);
        if (idCmd)
            TrayCommand(pfc, idCmd);
    }
}

//----------------------------------------------------------------------------
void _RunFileDlg(HWND hwnd, UINT idIcon, LPCITEMIDLIST pidlWorkingDir,
        UINT idTitle, UINT idPrompt, DWORD dwFlags)
{
    HICON hIcon = NULL;
    LPCTSTR lpszTitle = NULL;
    LPCTSTR lpszPrompt = NULL;
    TCHAR szTitle[256];
    TCHAR szPrompt[256];
    TCHAR szWorkingDir[MAX_PATH];
    int idDrive;

    dwFlags |= RFD_USEFULLPATHDIR;
    szWorkingDir[0] = 0;
    if (idIcon)
    {
        hIcon = LoadIcon(hinstCabinet, MAKEINTRESOURCE(idIcon));
    }
    if (!pidlWorkingDir || !SHGetPathFromIDList(pidlWorkingDir, szWorkingDir))
    {
        // This is either the Tray, or some non-file system folder, so
        // we will "suggest" the Desktop as a working dir, but if the
        // user types a full path, we will use that instead.  This is
        // what WIN31 Progman did (except they started in the Windows
        // dir instead of the Desktop).
        SHGetSpecialFolderPath(hwnd, szWorkingDir, CSIDL_DESKTOPDIRECTORY,
                               FALSE);
    }

    // if it's a removable dir, make sure it's still there
    if (szWorkingDir[0]) {
        idDrive = PathGetDriveNumber(szWorkingDir);
        if ((idDrive != -1))
        {
            UINT dtype = DriveType(idDrive);
            if ( ( (dtype == DRIVE_REMOVABLE) || (dtype == DRIVE_CDROM) )
                 && !PathFileExists(szWorkingDir) )
            {
                SHGetSpecialFolderPath(hwnd, szWorkingDir, CSIDL_DESKTOPDIRECTORY,
                                   FALSE);
            }
        }
    }

        if (idTitle)
        {
                LoadString(hinstCabinet, idTitle, szTitle, ARRAYSIZE(szTitle));
                lpszTitle = szTitle;
        }
        if (idPrompt)
        {
                LoadString(hinstCabinet, idPrompt, szPrompt, ARRAYSIZE(szPrompt));
                lpszPrompt = szPrompt;
        }

        RunFileDlg(hwnd, hIcon, szWorkingDir, lpszTitle, lpszPrompt, dwFlags);
}

BOOL SavePosEnumProc(HWND hwnd, LPARAM lParam)
{
    Assert(g_ts.pPositions);

    if (IsWindowVisible(hwnd) &&
        (hwnd != v_hwndTray) &&
        (hwnd != v_hwndDesktop))
    {
        HWNDANDPLACEMENT hap;

        hap.wp.length = SIZEOF(WINDOWPLACEMENT);
        GetWindowPlacement(hwnd, &hap.wp);
        DebugMsg(DM_TRACE, TEXT("SaveWindowPostitionsCallback %d %d"), hwnd, hap.wp.showCmd);
        if (hap.wp.showCmd != SW_SHOWMINIMIZED) {
            hap.hwnd = hwnd;
            DSA_InsertItem(g_ts.pPositions->hdsaWP, 0x7FFF, &hap);
        }
    }
    return TRUE;
}
void SaveWindowPositions(UINT idRes)
{
    if (g_ts.pPositions) {
        if (g_ts.pPositions->hdsaWP)
            DSA_DeleteAllItems(g_ts.pPositions->hdsaWP);
    } else {
        g_ts.pPositions = (LPWINDOWPOSITIONS)LocalAlloc(LPTR, SIZEOF(WINDOWPOSITIONS));
        if (!g_ts.pPositions)
            return;

        g_ts.pPositions->hdsaWP = DSA_Create(SIZEOF(HWNDANDPLACEMENT), 4);
    }
    g_ts.pPositions->idRes = idRes;

    EnumWindows(SavePosEnumProc, 0);
}

void RestoreWindowPositions()
{
    int i;
    LPHWNDANDPLACEMENT phap;
    LONG iAnimate;
    ANIMATIONINFO ami;

    if (!g_ts.pPositions)
        return;

    ami.cbSize = SIZEOF(ANIMATIONINFO);
    SystemParametersInfo(SPI_GETANIMATION, SIZEOF(ami), &ami, FALSE);
    iAnimate = ami.iMinAnimate;
    ami.iMinAnimate = FALSE;
    SystemParametersInfo(SPI_SETANIMATION, SIZEOF(ami), &ami, FALSE);

    i = DSA_GetItemCount(g_ts.pPositions->hdsaWP) - 1;

    for ( ; i >= 0; i--) {
        phap = DSA_GetItemPtr(g_ts.pPositions->hdsaWP, i);
        if (IsWindow(phap->hwnd)) {
#if (defined(DBCS) || defined(FE_IME))
        // Word6/J relies on WM_SYSCOMMAND/SC_RESTORE to invalidate their
        // MDI childrens' client area. If we just do SetWindowPlacement
        // the app leaves its MDI children unpainted. I wanted to patch
        // the app but couldn't afford to because the patch will remove
        // its optimizing code (will affect performance).
        // bug#11258-win95d.
        //
#if !defined(WINNT)
            if(ImmGetAppIMECompatFlags(GetWindowThreadProcessId(phap->hwnd, NULL)) & IMECOMPAT_SENDSC_RESTORE)
            {
                SendMessage(phap->hwnd, WM_SYSCOMMAND, SC_RESTORE, 0);
                continue;
            }
#endif
#endif
            phap->wp.length = SIZEOF(WINDOWPLACEMENT);
            if ((phap->wp.showCmd) == SW_SHOWMAXIMIZED)
            {
                ShowWindowAsync(phap->hwnd, SW_MAXIMIZE);
            }
            else
            {
                // If the window to be undone is now iconic, we just need
                // to restore it off the tray, otherwise (as in undoing from
                // a tile) we need to reset its position

                if (IsIconic(phap->hwnd))
                {
                    ShowWindowAsync(phap->hwnd, SW_RESTORE);
                }
                else
                {
                    SetWindowPos(phap->hwnd,
                                 NULL,
                                 phap->wp.rcNormalPosition.left,
                                 phap->wp.rcNormalPosition.top,
                                 phap->wp.rcNormalPosition.right - phap->wp.rcNormalPosition.left,
                                 phap->wp.rcNormalPosition.bottom - phap->wp.rcNormalPosition.top,
                                 SWP_NOZORDER | SWP_NOACTIVATE | SWP_ASYNCWINDOWPOS);
                }
            }
        }
    }

    ami.iMinAnimate = iAnimate;
    SystemParametersInfo(SPI_SETANIMATION, SIZEOF(ami), &ami, FALSE);

    DestroySavedWindowPositions();

}

void DestroySavedWindowPositions()
{
    // free the global struct
    DSA_Destroy(g_ts.pPositions->hdsaWP);
    LocalFree(g_ts.pPositions);
    g_ts.pPositions = 0;
}

void TrayHandleWindowDestroyed(HWND hwnd)
{
    int i;
    LPHWNDANDPLACEMENT phap;

    Assert(g_ts.pPositions);

    i = DSA_GetItemCount(g_ts.pPositions->hdsaWP) - 1;
    for ( ; i >= 0; i--) {
        phap = DSA_GetItemPtr(g_ts.pPositions->hdsaWP, i);
        if (phap->hwnd == hwnd || !IsWindow(phap->hwnd)) {
            DSA_DeleteItem(g_ts.pPositions->hdsaWP, i);
        }
    }

    if (!DSA_GetItemCount(g_ts.pPositions->hdsaWP))
        DestroySavedWindowPositions();
}


//----------------------------------------------------------------------------
// Allow us to bump the activation of the run dlg hidden window.
// Certain lame apps (Norton Desktop setup) use the active window at RunDlg time
// as the parent for their dialogs. If that window disappears then they fault.
// We don't want the tray to get the activation coz it will cause it to appeare
// if you're in auto-hide mode.
LRESULT CALLBACK RunDlgStaticSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (uMsg == RDM_ACTIVATE) {

        // there's a window out there... activate it
        SwitchToThisWindow(GetLastActivePopup(hWnd), TRUE);

        return 0;

    } else if ((uMsg == WM_ACTIVATE) && (wParam == WA_ACTIVE))
        {
                // Bump the activation to the desktop.
                if (v_hwndDesktop)
                {
                        SetForegroundWindow(v_hwndDesktop);
                        return 0;
                }
        } else if (uMsg == WM_NOTIFY) {
            // relay it to the tray
            return SendMessage(v_hwndTray, uMsg, wParam, lParam);
        }

        return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

const TCHAR c_szRunDlgReady[] = TEXT("MSShellRunDlgReady");
const TCHAR c_szWaitingThreadID[] = TEXT("WaitingThreadID");

DWORD WINAPI RunDlgThread(LPVOID data)
{
    HWND hwnd;
    RECT rc;
    GetWindowRect(g_ts.hwndStart, &rc);
    hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, c_szStatic, NULL, 0   ,
        rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, hinstCabinet, NULL);
    // Subclass it.
    SetWindowLong(hwnd, GWL_WNDPROC, (LONG)(FARPROC)RunDlgStaticSubclassWndProc);

    if (hwnd) {
        // SwitchToThisWindow(hwnd, TRUE);
        // SetForegroundWindow(hwnd);
        HANDLE hMemWorkDir = NULL;
        LPITEMIDLIST pidlWorkingDir = NULL;
#ifdef WINNT
        TCHAR szDir[MAX_PATH];
        TCHAR szPath[MAX_PATH];
        BOOL fSimple;
#endif

        if (data)
            SetProp(hwnd, c_szWaitingThreadID, (HANDLE)data);

#ifdef WINNT
        // On NT, we like to start apps in the HOMEPATH directory.  This
        // should be the current directory for the current process.
        fSimple = FALSE;
        GetEnvironmentVariable(TEXT("HOMEDRIVE"), szDir, ARRAYSIZE(szDir));
        GetEnvironmentVariable(TEXT("HOMEPATH"), szPath, ARRAYSIZE(szPath));

        if (PathAppend(szDir,szPath) && PathIsDirectory(szDir))
        {
            pidlWorkingDir = SHSimpleIDListFromPath(szDir);
            fSimple = TRUE;
        }
#endif

        if (!pidlWorkingDir)
        {
            // If the last active window was a folder/explorer window with the
            // desktop as root, use its as the current dir
            if (g_ts.hwndLastActive && !IsMinimized(g_ts.hwndLastActive) &&
                (Cabinet_IsExplorerWindow(g_ts.hwndLastActive) || Cabinet_IsFolderWindow(g_ts.hwndLastActive))
                && Desktop_IsSameRoot(g_ts.hwndLastActive,NULL))
            {
// BUGBUG: BobDay - If the window is hung, shell hangs!
// Should ask OTRegister for appropriate COMPAREROOT data
                hMemWorkDir = (HANDLE)SendMessage(g_ts.hwndLastActive, CWM_CLONEPIDL, GetCurrentProcessId(), 0L);
                pidlWorkingDir = SHLockShared(hMemWorkDir,GetCurrentProcessId());
            }
        }

        _RunFileDlg(hwnd, 0, pidlWorkingDir, 0, 0, 0);

        if (pidlWorkingDir)
        {
#ifdef WINNT
            if (fSimple)
                ILFree(pidlWorkingDir);
            else
#endif
            {
                SHUnlockShared(pidlWorkingDir);
                SHFreeShared(hMemWorkDir,GetCurrentProcessId());
            }
        }

        if (data)
            RemoveProp(hwnd, c_szWaitingThreadID);
        DestroyWindow(hwnd);
    }
    return TRUE;
}

void Tray_RunDlg()
{
    HANDLE ht;
    DWORD id;
    HANDLE hEvent;
    LPVOID pvThreadParam;

    if (!_Restricted(v_hwndTray, REST_NORUN))
    {
        TCHAR szRunDlgTitle[MAX_PATH];
        HWND  hwndOldRun;
        LoadString(hinstCabinet, IDS_RUNDLGTITLE, szRunDlgTitle, ARRAYSIZE(szRunDlgTitle));

        // See if there is already a run dialog up, and if so, try to activate it

        hwndOldRun = FindWindow(WC_DIALOG, szRunDlgTitle);
        if (hwndOldRun)
        {
            DWORD dwPID;

            GetWindowThreadProcessId(hwndOldRun, &dwPID);
            if (dwPID == GetCurrentProcessId())
            {
                if (IsWindowVisible(hwndOldRun))
                {
                    SetWindowPos(hwndOldRun, HWND_TOP, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);
                    return;
                }
            }
        }

        // Create an event so we can wait for the run dlg to appear before
        // continue - this allows it to capture any type-ahead.
        hEvent = CreateEvent(NULL, TRUE, FALSE, c_szRunDlgReady);
        if (hEvent)
            pvThreadParam = (LPVOID)GetCurrentThreadId();
        else
            pvThreadParam = NULL;

        ht = CreateThread(NULL, 0, RunDlgThread, pvThreadParam, 0, &id);
        if (ht) {
            if (hEvent)
            {
                MsgWaitForMultipleObjectsLoop(hEvent, 10*1000);
                DebugMsg(DM_TRACE, TEXT("c.t_rd: Done waiting."));
                CloseHandle(hEvent);
            }
            CloseHandle(ht);
        }
    }
}

DWORD MsgWaitForMultipleObjectsLoop(HANDLE hEvent, DWORD dwTimeout)
{
    MSG msg;
    DWORD dwObject;
    // DebugMsg(DM_TRACE, "c.t_rd: Waiting for run dlg...");
    while (1)
    {
        // NB We need to let the run dialog become active so we have to half handle sent
        // messages but we don't want to handle any input events or we'll swallow the
        // type-ahead.
        dwObject = MsgWaitForMultipleObjects(1, &hEvent, FALSE, dwTimeout, QS_SENDMESSAGE);
        // Are we done waiting?
        switch (dwObject) {
        case WAIT_OBJECT_0:
        case WAIT_FAILED:
            return dwObject;

        case WAIT_OBJECT_0 + 1:
            // Almost.
            // DebugMsg(DM_TRACE, "c.t_rd: Almost done waiting.");
            PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
            break;
        }
    }
    // never gets here
    // return dwObject;
}

void ExploreCommonStartMenu(BOOL bExplore)
{
   STARTUPINFO si;
   PROCESS_INFORMATION pi;
   TCHAR szPath[MAX_PATH];
   TCHAR szCmdLine[MAX_PATH + 50];


   //
   // Get the common start menu path.
   //

   if (!SHGetSpecialFolderPath(NULL, szPath, CSIDL_COMMON_STARTMENU, TRUE)) {
       return;
   }


   //
   // If we are starting in explorer view, then the command line
   // has a "/e," before the quoted diretory.
   //

   if (bExplore) {
       lstrcpy (szCmdLine, TEXT("explorer.exe /e,\""));
   } else {
       lstrcpy (szCmdLine, TEXT("explorer.exe \""));
   }

   lstrcat (szCmdLine, szPath);
   lstrcat (szCmdLine, TEXT("\""));



   //
   // Initialize process startup info
   //

   si.cb = sizeof(STARTUPINFO);
   si.lpReserved = NULL;
   si.lpTitle = NULL;
   si.lpDesktop = NULL;
   si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L;
   si.dwFlags = STARTF_USESHOWWINDOW;
   si.wShowWindow = SW_SHOWNORMAL;
   si.lpReserved2 = NULL;
   si.cbReserved2 = 0;


   //
   // Start explorer
   //
   if (CreateProcess(NULL, szCmdLine, NULL, NULL, FALSE,
                     NORMAL_PRIORITY_CLASS, NULL, NULL,
                     &si, &pi)) {

       //
       // Close the process and thread handles
       //

       CloseHandle(pi.hProcess);
       CloseHandle(pi.hThread);
   }

}


void StartMenuFolder_ContextMenu(PFileCabinet pfc, DWORD dwPos)
{
    LPITEMIDLIST pidlStart = SHCloneSpecialIDList(v_hwndTray, CSIDL_STARTMENU, TRUE);
    Tray_HandleFullScreenApp(FALSE, NULL);
    SetForegroundWindow(pfc->hwndMain);
    if (pidlStart) {
        LPITEMIDLIST pidlLast = ILClone(ILFindLastID(pidlStart));
        ILRemoveLastID(pidlStart);

        if (pidlLast)
        {
            LPSHELLFOLDER psf;
            if (SUCCEEDED(s_pshfRoot->lpVtbl->BindToObject(s_pshfRoot,
                pidlStart, NULL, &IID_IShellFolder, &psf)))
            {
                HMENU hmenu = CreatePopupMenu();
                if (hmenu)
                {
                    LPCONTEXTMENU pcm;
                    HRESULT hres = psf->lpVtbl->GetUIObjectOf(psf, v_hwndTray,
                        1, &pidlLast, &IID_IContextMenu, NULL, &pcm);
                    if (SUCCEEDED(hres))
                    {
                        hres = pcm->lpVtbl->QueryContextMenu(pcm, hmenu, 0,
                                            IDSYSPOPUP_FIRST, IDSYSPOPUP_LAST, CMF_VERBSONLY);

                        if (SUCCEEDED(hres))
                        {
                            int idCmd;

                            if (!SHRestricted(REST_NOCOMMONGROUPS)) {
                                TCHAR szCommon[50];

                                if (g_bIsUserAnAdmin) {
                                   AppendMenu (hmenu, MF_SEPARATOR, 0, NULL);


                                   LoadString (hinstCabinet, IDS_OPENCOMMON,
                                               szCommon, 50);
                                   AppendMenu (hmenu, MF_STRING, IDSYSPOPUP_LAST - 1,
                                               szCommon);


                                   LoadString (hinstCabinet, IDS_EXPLORECOMMON,
                                               szCommon, 50);
                                   AppendMenu (hmenu, MF_STRING, IDSYSPOPUP_LAST,
                                               szCommon);
                                }
                            }


                            if (dwPos == (DWORD)-1)
                            {
                                idCmd = Tray_TrackMenu(pfc, hmenu, FALSE);
                            }
                            else
                            {
                                SendMessage(g_ts.hwndTrayTips, TTM_ACTIVATE, FALSE, 0L);
                                idCmd = TrackPopupMenu(hmenu,
                                                       TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
                                                       LOWORD(dwPos), HIWORD(dwPos), 0, v_hwndTray, NULL);
                                SendMessage(g_ts.hwndTrayTips, TTM_ACTIVATE, TRUE, 0L);
                            }

                            if (idCmd)
                            {

                                if (idCmd == (IDSYSPOPUP_LAST - 1)) {
                                    ExploreCommonStartMenu(FALSE);

                                } else if (idCmd == IDSYSPOPUP_LAST) {
                                    ExploreCommonStartMenu(TRUE);

                                } else {

                                    TCHAR szPath[MAX_PATH];
                                    CMINVOKECOMMANDINFOEX ici = {
                                        SIZEOF(CMINVOKECOMMANDINFOEX),
                                        0L,
                                        v_hwndTray,
                                        (LPSTR)MAKEINTRESOURCE(idCmd - IDSYSPOPUP_FIRST),
                                        NULL, NULL,
                                        SW_NORMAL,
                                    };
#ifdef UNICODE
                                    CHAR szPathAnsi[MAX_PATH];
                                    SHGetPathFromIDListA(pidlStart, szPathAnsi);
                                    SHGetPathFromIDList(pidlStart, szPath);
                                    ici.lpDirectory = szPathAnsi;
                                    ici.lpDirectoryW = szPath;
                                    ici.fMask |= CMIC_MASK_UNICODE;
#else
                                    SHGetPathFromIDList(pidlStart, szPath);
                                    ici.lpDirectory = szPath;
#endif
                                    pcm->lpVtbl->InvokeCommand(pcm,
                                                    (LPCMINVOKECOMMANDINFO)&ici);
                                }
                            }
                        }
                        pcm->lpVtbl->Release(pcm);
                    }
                    DestroyMenu(hmenu);
                }
                psf->lpVtbl->Release(psf);
            }
            ILFree(pidlLast);
        }
        ILFree(pidlStart);
    }
}
//----------------------------------------------------------------------------
void Tray_Suspend(void)
{
    DECLAREWAITCURSOR;

    SetWaitCursor();
#if 1
    SetSystemPowerState(FALSE, FALSE);
#else
    // See RemoveSuspendMenu comment as to why this is ifdef'd out but not
    // (yet) deleted.  13-Oct-94
    if (!SetSystemPowerState(FALSE, FALSE) &&
        GetLastError() == ERROR_SET_POWER_STATE_FAILED)
        RemoveSuspendMenu(pfc->hwndMain);
#endif
    ResetWaitCursor();
}

#ifdef WINNT

// RunSystemMonitor
//
// Launches system monitor (taskmgr.exe), which is expected to be able
// to find any currently running instances of itself

void RunSystemMonitor(void)
{
    STARTUPINFO startup;
    PROCESS_INFORMATION pi;
    TCHAR szName[] = TEXT("taskmgr.exe");

    startup.cb = SIZEOF(startup);
    startup.lpReserved = NULL;
    startup.lpDesktop = NULL;
    startup.lpTitle = NULL;
    startup.dwFlags = 0L;
    startup.cbReserved2 = 0;
    startup.lpReserved2 = NULL;
    startup.wShowWindow = SW_SHOWNORMAL;

    if (CreateProcess(NULL, szName, NULL, NULL, FALSE, 0,
                      NULL, NULL, &startup, &pi))
    {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }

}

#endif

void HandleGlobalHotkey(WPARAM wParam);

void TrayCommand(PFileCabinet pfc, UINT idCmd)
{
    switch (idCmd) {
    // NB Everything off the programs menu, cascades and all comes in as an IDM_PROGRAMS.
    // Sim for recent docs.
    case IDM_STARTMENU:
    case IDM_RECENT:
    case IDM_PROGRAMS:
        FileMenu_HandleCommand(pfc->hwndMain, idCmd);
        break;

    case IDM_CONTROLS:
    case IDM_PRINTERS:
    // case IDM_PROGRAMSFOLDER:
    // case IDM_FONTS:
        ShowFolder(pfc->hwndMain, idCmd, COF_USEOPENSETTINGS);
        break;

#ifndef WINNT       // BUGBUG - Fix this when NT supports APM & Docking
    case IDM_SUSPEND:
        Tray_Suspend();
        break;

    case IDM_EJECTPC:
        DoEjectPC();
        break;
#endif

    case IDM_EXITWIN:
        DoExitWindows(pfc->hwndMain);
        break;

    case IDM_FILERUN:
        Tray_RunDlg();
        break;

    case IDM_MINIMIZEALLHOTKEY:
        HandleGlobalHotkey(GHID_MINIMIZEALL);
        break;

    case IDM_MINIMIZEALL:
        // minimize all window
        MinimizeAll(pfc->hwndView);
        break;

    case IDM_UNDO:
        RestoreWindowPositions();
        break;

    case IDM_SETTIME:
        // run the default applet in timedate.cpl
        SHRunControlPanel( TEXT("timedate.cpl"), pfc->hwndMain );
        break;

#ifdef WINNT
    case IDM_SHOWTASKMAN:
        RunSystemMonitor();
        break;
#endif

    case IDM_CASCADE:
    case IDM_VERTTILE:
    case IDM_HORIZTILE:

        if (CanTileAnyWindows()) {
            SaveWindowPositions((idCmd == IDM_CASCADE) ? IDS_CASCADE : IDS_TILE );
            AppBarNotifyAll(ABN_WINDOWARRANGE, NULL, TRUE);
            if (idCmd == IDM_CASCADE)
                CascadeWindows(GetDesktopWindow(), 0, NULL, 0, NULL);
            else
                TileWindows(GetDesktopWindow(), idCmd == IDM_VERTTILE ?
                            MDITILE_VERTICAL : MDITILE_HORIZONTAL, NULL, 0, NULL);
            AppBarNotifyAll(ABN_WINDOWARRANGE, NULL, FALSE);
        }
        break;

    case IDM_TRAYPROPERTIES:
        DoTrayProperties(pfc);
        break;

     // REVIEW: windows.hlp should be a string table resource

    case IDM_HELPSEARCH:
        WinHelp(pfc->hwndMain, c_szWindowsHlp, HELP_FINDER, 0);
        break;

    // NB The Alt-s comes in here.
    case IDC_KBSTART:
        // This pushes the start button and causes the start menu to popup.
        SendMessage(GetDlgItem(pfc->hwndMain, IDC_START), BM_SETSTATE, TRUE, 0);
        // This forces the button back up.
        SendMessage(GetDlgItem(pfc->hwndMain, IDC_START), BM_SETSTATE, FALSE, 0);
        break;

    case IDC_ASYNCSTART:
        // Make sure the button is down.
        // DebugMsg(DM_TRACE, "c.twp: IDC_START.");

        // Make sure the Start button is down.
        if (SendMessage(g_ts.hwndStart, BM_GETSTATE, 0, 0) & 0x0004)
        {
            // DebugMsg(DM_TRACE, "c.twp: Start button down.");
            // Set the focus.
            SetFocus(g_ts.hwndStart);
            ToolbarMenu(pfc);
        }
        else
        {
            // DebugMsg(DM_TRACE, "c.twp: Start button up.");
        }
        break;

    // NB LButtonDown on the Start button come in here.
    // Space-bar stuff also comes in here.
    case IDC_START:
        // User gets a bit confused with space-bar tuff (the popup ends up
        // getting the key-up and beeps).
        PostMessage(pfc->hwndMain, WM_COMMAND, IDC_ASYNCSTART, 0);
        break;

    case FCIDM_FINDFILES:
        SHFindFiles(NULL, NULL);
        break;

    case FCIDM_FINDCOMPUTER:
        SHFindComputer(NULL, NULL);
        break;

    case FCIDM_REFRESH:
        RebuildEntireMenu(pfc);
        break;

    case FCIDM_NEXTCTL:
        {
            HWND hwndFocus = GetFocus();
            BOOL fShift = (GetAsyncKeyState(VK_SHIFT) < 0);
            if (hwndFocus == pfc->hwndToolbar) {
                SetFocus(fShift ? v_hwndDesktop : pfc->hwndView);
            } else if (hwndFocus == pfc->hwndView || IsChild(pfc->hwndView, hwndFocus)) {
                SetFocus(fShift ? pfc->hwndToolbar : v_hwndDesktop);
            } else {
                SetFocus(fShift ? pfc->hwndView : pfc->hwndToolbar);
            }
            break;
        }

    case IDM_RETURN:
        DebugMsg(DM_TRACE, TEXT("c.tc: Return key hit."));
        if (GetFocus() == g_ts.hwndStart)
        {
            PostMessage(pfc->hwndMain, WM_COMMAND, IDC_KBSTART, 0);
        }
        else
        {
            SendMessage(GetDlgItem(pfc->hwndView, IDC_FOLDERTABS), WM_KEYDOWN, VK_RETURN, 0);
        }
        break;

    default:
        if (IsInRange(idCmd, TRAY_IDM_FINDFIRST, TRAY_IDM_FINDLAST) && !_Restricted(v_hwndTray, REST_NOFIND)) {
            CMINVOKECOMMANDINFOEX ici = {
                SIZEOF(CMINVOKECOMMANDINFOEX),
                0L,
                pfc->hwndMain,
                (LPSTR)MAKEINTRESOURCE(idCmd - TRAY_IDM_FINDFIRST),
                NULL, NULL,
                SW_NORMAL,
            };

            pfc->pcmFind->lpVtbl->InvokeCommand(pfc->pcmFind,
                                                (LPCMINVOKECOMMANDINFO)&ici);

        } else {
            DebugMsg(DM_ERROR, TEXT("tray Unknown command (%x)"), idCmd);
        }
        break;
    }
}


//// Start menu/Tray tab as a drop target
extern IDropTarget c_dtgtStart;
extern IDropTarget c_dtgtTab;
extern IDropTarget c_dtgtTray;

//=============================================================================
// CDVDropTarget : member
//=============================================================================
STDMETHODIMP CStartDropTarget_QueryInterface(LPDROPTARGET pdtgt, REFIID riid, LPVOID * ppvObj)
{
    if (IsEqualIID(riid, &IID_IDropTarget) || IsEqualIID(riid, &IID_IUnknown))
    {
        *ppvObj = pdtgt;
        return S_OK;
    }

    *ppvObj = NULL;
    return (E_NOINTERFACE);
}


STDMETHODIMP_(ULONG) CStartDropTarget_AddRef(LPDROPTARGET pdtgt)
{
    return 2;
}

STDMETHODIMP_(ULONG) CStartDropTarget_Release(LPDROPTARGET pdtgt)
{
    return 1;
}

STDMETHODIMP CStartDropTarget_DragEnter(LPDROPTARGET pdtgt, LPDATAOBJECT pdtobj, DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect)
{
    HWND hwndLock = v_hwndTray;
    RECT rcLockWindow;
    POINT pt;
    DebugMsg(DM_TRACE, TEXT("sh - TR CStartDropTarget::DragEnter called"));

    TraySetUnhideTimer(ptl.x, ptl.y);

    GetWindowRect(hwndLock, &rcLockWindow);
    pt.x = ptl.x-rcLockWindow.left;
    pt.y = ptl.y-rcLockWindow.top;
    DAD_DragEnterEx(hwndLock, pt);
    if (pdtgt==&c_dtgtStart) {
        *pdwEffect = DROPEFFECT_LINK;
    } else if (pdtgt==&c_dtgtTab) {
        *pdwEffect = DROPEFFECT_MOVE | DROPEFFECT_SCROLL;
    } else {
        *pdwEffect = DROPEFFECT_NONE;
    }

    return S_OK;
}


STDMETHODIMP CStartDropTarget_DragOver(LPDROPTARGET pdtgt, DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect)
{
    POINT pt = { ptl.x, ptl.y };

    TraySetUnhideTimer(ptl.x, ptl.y);

    ScreenToClient(v_hwndTray, &pt);
    DAD_DragMove(pt);
    *pdwEffect = DROPEFFECT_LINK;

    return S_OK;
}

#define MSEC_ACTIVATE (1*1000)

void CTabDropTarget_HitTest(BOOL fEnter, POINTL ptl)
{
    extern int Task_HitTest(HWND hwndTask, POINTL ptl);
    extern void Task_SetCurSel(HWND hwndTask, int i);
    static int   s_iHit = -1;
    static DWORD s_dwBegin = 0;
    static BOOL  s_fWaiting = FALSE;

    LPFileCabinet pfc = g_pfcTray;
    if (!pfc) {
        return;
    }

    if (fEnter) {
        s_iHit = -1;
        s_fWaiting = FALSE;
    } else {
        int iHitNew = Task_HitTest(pfc->hwndView, ptl);

        if (s_iHit != iHitNew)
        {
            DebugMsg(DM_TRACE, TEXT("ca TR - CTDT::HitTest new target (%d->%d)"), s_iHit, iHitNew);
            s_iHit = iHitNew;
            if (iHitNew != -1)
            {
                s_dwBegin = GetCurrentTime();
                s_fWaiting = TRUE;
            }
            else
            {
                s_fWaiting = FALSE;
            }
        }
        else if (s_fWaiting)
        {
            DWORD dwWaited = GetCurrentTime()-s_dwBegin;
            DebugMsg(DM_TRACE, TEXT("ca TR - CTDT::HitTest waiting... (%d on %x)"), dwWaited, s_iHit);
            if (dwWaited > MSEC_ACTIVATE)
            {
                DAD_ShowDragImage(FALSE);       // unlock the drag sink if we are dragging.
                Task_SetCurSel(pfc->hwndView, s_iHit);
                UpdateWindow(v_hwndTray);
                DAD_ShowDragImage(TRUE);        // restore the lock state.
                s_fWaiting = FALSE;
            }
        }
    }
}

STDMETHODIMP CTabDropTarget_DragEnter(LPDROPTARGET pdtgt, LPDATAOBJECT pdtobj, DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect)
{
    CTabDropTarget_HitTest(TRUE, ptl);

    return CStartDropTarget_DragEnter(pdtgt, pdtobj, grfKeyState, ptl, pdwEffect);
}

STDMETHODIMP CTabDropTarget_DragOver(LPDROPTARGET pdtgt, DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect)
{
    CTabDropTarget_HitTest(FALSE, ptl);
    CStartDropTarget_DragOver(pdtgt, grfKeyState, ptl, pdwEffect);
    *pdwEffect = (pdtgt==&c_dtgtTab) ? (DROPEFFECT_MOVE|DROPEFFECT_SCROLL) : DROPEFFECT_NONE;
    return S_OK;
}

STDMETHODIMP CStartDropTarget_DragLeave(LPDROPTARGET pdtgt)
{
    DebugMsg(DM_TRACE, TEXT("sh - TR CStartDropTarget::DragLeave called"));
    DAD_DragLeave();
    return S_OK;
}

STDMETHODIMP CStartDropTarget_Drop(LPDROPTARGET pdtgt, LPDATAOBJECT pdtobj,
                             DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
    LPITEMIDLIST pidlStart = SHCloneSpecialIDList(v_hwndTray, CSIDL_STARTMENU, TRUE);
    LPSHELLFOLDER psf;
    HRESULT hres = (E_OUTOFMEMORY);

    if (pidlStart) {
        hres = s_pshfRoot->lpVtbl->BindToObject(s_pshfRoot, pidlStart, NULL, &IID_IShellFolder, &psf);
        if (SUCCEEDED(hres))
        {
            IDropTarget *pdrop;
            hres = psf->lpVtbl->CreateViewObject(psf, v_hwndTray, &IID_IDropTarget, &pdrop);
            if (SUCCEEDED(hres))
            {
                POINTL pt = { 0, 0 };
                DWORD grfKeyState;

                *pdwEffect &= DROPEFFECT_LINK;
                grfKeyState = 0;

                pdrop->lpVtbl->DragEnter(pdrop, pdtobj, grfKeyState, pt,
                                         pdwEffect);

                hres = pdrop->lpVtbl->Drop(pdrop, pdtobj, grfKeyState, pt,
                                           pdwEffect);
                if (SUCCEEDED(hres) && *pdwEffect) {
                    Sleep(SECOND);
                    g_ts.fFavInvalid = TRUE;
                }
                pdrop->lpVtbl->DragLeave(pdrop);
                pdrop->lpVtbl->Release(pdrop);
            }

            psf->lpVtbl->Release(psf);
        }

        ILFree(pidlStart);
    }

    DAD_DragLeave();

    return hres;
}

STDMETHODIMP CTabDropTarget_Drop(LPDROPTARGET pdtgt, LPDATAOBJECT pdtobj,
                             DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
    if (pdtgt==&c_dtgtTab)
    {
        LPFileCabinet pfc = g_pfcTray;
    DAD_DragLeave();
    if (pfc)
    {
        ShellMessageBox(hinstCabinet, pfc->hwndMain,
               MAKEINTRESOURCE(IDS_TASKDROP_ERROR),
               MAKEINTRESOURCE(IDS_TASKBAR),
               MB_ICONHAND | MB_OK);
    }

        //
        // We should not return the 'move' bit.
        //
        if (*pdwEffect & DROPEFFECT_COPY) {
            *pdwEffect = DROPEFFECT_COPY;
        } else {
            *pdwEffect &= DROPEFFECT_LINK;
        }
    }
    return S_OK;
}

// store our drop target in pdtgtTree
//=============================================================================
// CStartDropTarget : Register and Revoke
//=============================================================================
void CStartDropTarget_Register(LPFileCabinet pfc)
{
    g_cfIDList = RegisterClipboardFormat(CFSTR_SHELLIDLIST);
    SHRegisterDragDrop(g_ts.hwndStart, &c_dtgtStart);
    SHRegisterDragDrop(pfc->hwndView, &c_dtgtTab);
    SHRegisterDragDrop(pfc->hwndMain, &c_dtgtTray);
}

void CStartDropTarget_Revoke(LPFileCabinet pfc)
{
    SHRevokeDragDrop(pfc->hwndMain);
    SHRevokeDragDrop(pfc->hwndView);
    SHRevokeDragDrop(g_ts.hwndStart);
}

//=============================================================================
// CStartDropTarget : VTable
//=============================================================================
IDropTargetVtbl c_CStartDropTargetVtbl = {
    CStartDropTarget_QueryInterface,
    CStartDropTarget_AddRef,
    CStartDropTarget_Release,
    CStartDropTarget_DragEnter,
    CStartDropTarget_DragOver,
    CStartDropTarget_DragLeave,
    CStartDropTarget_Drop
};

IDropTarget c_dtgtStart = { &c_CStartDropTargetVtbl };

IDropTargetVtbl c_CTabDropTargetVtbl = {
    CStartDropTarget_QueryInterface,
    CStartDropTarget_AddRef,
    CStartDropTarget_Release,
    CTabDropTarget_DragEnter,
    CTabDropTarget_DragOver,
    CStartDropTarget_DragLeave,
    CTabDropTarget_Drop
};

IDropTarget c_dtgtTab = { &c_CTabDropTargetVtbl };
IDropTarget c_dtgtTray = { &c_CTabDropTargetVtbl };

//// end Start Drop target


////////////////////////////////////////////////////////////////////////////
// Begin Print Notify stuff
////////////////////////////////////////////////////////////////////////////

typedef BOOL (* PFNENUMPRINTERS) (DWORD, LPTSTR, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD);
typedef BOOL (* PFNENUMJOBS) (HANDLE, DWORD, DWORD, DWORD, LPBYTE, DWORD, LPDWORD, LPDWORD);
typedef BOOL (* PFNOPENPRINTER) (LPTSTR, LPHANDLE, LPVOID);
typedef BOOL (* PFNCLOSEPRINTER) (HANDLE);

PFNENUMPRINTERS g_pfnEnumPrinters = NULL;
PFNENUMJOBS g_pfnEnumJobs = NULL;
PFNOPENPRINTER g_pfnOpenPrinter = NULL;
PFNCLOSEPRINTER g_pfnClosePrinter = NULL;

TCHAR const c_szSpoolClass[] = TEXT("SpoolProcessClass");

void PrintNotify_Init(HWND hWnd)
{
    g_ts.pidlPrintersFolder = SHCloneSpecialIDList(hWnd, CSIDL_PRINTERS, FALSE);
    if (g_ts.pidlPrintersFolder)
    {
        SHChangeNotifyEntry fsne;
        HWND hwndSpool;

        fsne.pidl = g_ts.pidlPrintersFolder;
        fsne.fRecursive = TRUE;

        DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - registering print notify icon"));
        g_ts.uPrintNotify = SHChangeNotifyRegister(hWnd, SHCNRF_NewDelivery | SHCNRF_ShellLevel,
                                SHCNE_CREATE | SHCNE_UPDATEITEM | SHCNE_DELETE,
                                WMTRAY_PRINTCHANGE, 1, &fsne);

        // Tell the spool subsystem to stop waiting for the printnotify icon..
        // After this notification, the subsystem can start printing and our
        // little printer icon will work. We need this so that deferred
        // print jobs don't start printing before the icon is listening.
        hwndSpool = FindWindow(c_szSpoolClass, NULL);
        if (hwndSpool)
        {
            NMHDR nmhdr = {hWnd, 0, NM_ENDWAIT};
            DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Telling print subsystem we're here"));
            SendMessage(hwndSpool, WM_NOTIFY, (WPARAM)NM_ENDWAIT, (LPARAM)&nmhdr);
        }
    }
}


typedef BOOL (*ENUMPROP)(LPVOID lpData, HANDLE hPrinter, DWORD dwLevel,
        LPBYTE pEnum, DWORD dwSize, DWORD *lpdwNeeded, DWORD *lpdwNum);

LPVOID Printer_EnumProps(HANDLE hPrinter, DWORD dwLevel, DWORD *lpdwNum,
    ENUMPROP lpfnEnum, LPVOID lpData)
{
    DWORD dwSize, dwNeeded;
    LPBYTE pEnum;

    dwSize = 0;
    SetLastError(0);
    lpfnEnum(lpData, hPrinter, dwLevel, NULL, 0, &dwSize, lpdwNum);
    if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)
    {
        pEnum = NULL;
        goto Error1;
    }

    Assert(dwSize < 0x100000L);

TryAgain:
    pEnum = Alloc(dwSize);
    if (!pEnum)
    {
        goto Error1;
    }

    SetLastError(0);
    if (!lpfnEnum(lpData, hPrinter, dwLevel, pEnum, dwSize, &dwNeeded, lpdwNum))
    {
        Free(pEnum);

        if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
        {
            dwSize = dwNeeded;
            goto TryAgain;
        }

        pEnum = NULL;
    }

Error1:
    return(pEnum);
}


BOOL _EnumPrintersCB(LPVOID lpData, HANDLE hPrinter, DWORD dwLevel,
    LPBYTE pEnum, DWORD dwSize, DWORD *lpdwNeeded, DWORD *lpdwNum)
{
    return(g_pfnEnumPrinters((DWORD)lpData, NULL, dwLevel, pEnum, dwSize, lpdwNeeded, lpdwNum));
}


DWORD _EnumPrinters(DWORD dwType, DWORD dwLevel, LPVOID *ppPrinters)
{
    DWORD dwNum = 0L;
    *ppPrinters = Printer_EnumProps(NULL, dwLevel, &dwNum, _EnumPrintersCB, (LPVOID)dwType);
    if (*ppPrinters==NULL)
    {
        dwNum=0;
    }
    return(dwNum);
}


BOOL _EnumJobsCB(LPVOID lpData, HANDLE hPrinter, DWORD dwLevel,
    LPBYTE pEnum, DWORD dwSize, DWORD *lpdwNeeded, DWORD *lpdwNum)
{
    return(g_pfnEnumJobs(hPrinter, 0, 0xffff, dwLevel, pEnum, dwSize, lpdwNeeded, lpdwNum));
}


DWORD _EnumJobs(HANDLE hPrinter, DWORD dwLevel, LPVOID *ppJobs)
{
    DWORD dwNum = 0L;
    *ppJobs = Printer_EnumProps(hPrinter, dwLevel, &dwNum, _EnumJobsCB, NULL);
    if (*ppJobs==NULL)
    {
        dwNum=0;
    }
    return(dwNum);
}


// Thread_PrinterPoll is the worker thread for PrintNotify_HandleFSNotify.
// It keeps track of print jobs, polls the pinter, updates the icon, etc.

typedef struct _PRINTERPOLLDATA
{
    HWND hWnd;
    HANDLE heWakeUp;
} PRINTERPOLLDATA, *PPRINTERPOLLDATA;

DWORD WINAPI Thread_PrinterPoll(PPRINTERPOLLDATA pData)
{
    PRINTERPOLLDATA data;

    HANDLE hmodWinspool;

    TCHAR szUserName[40];
    TCHAR szFormat[60];
    DWORD dwSize;
    DWORD aArgs[2];

    NOTIFYICONDATA IconData;
    DWORD dwNotifyMessage = NIM_ADD;
    UINT nIconShown = 0;
    HICON hIconNormal = NULL;
    HICON hIconError = NULL;

    HANDLE haPrinterNames = NULL;
    LPSHELLFOLDER psf = NULL;
    DWORD dwTimeOut;

    DWORD dwStatus;
    int nFound;

    MSG msg;

    LPITEMIDLIST pidlFree = NULL;

    static int s_cxSmIcon = 0;
    static int s_cySmIcon = 0;
    int cxSmIcon = 0;
    int cySmIcon = 0;

    // notify PrintNotify_StartThread that our message queue is up
    PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
    data = *pData;
    GlobalFree(pData);
    SetEvent(data.heWakeUp);

    if (!SFCInitializeThread())
    {
        goto Error0;
    }

    // load winspool functions we use
    hmodWinspool = LoadLibrary(TEXT("winspool.drv"));
    if (!hmodWinspool)
    {
        goto Error1;
    }

    // These are globals just so I don't have to pass them around
#ifdef UNICODE
    g_pfnEnumPrinters = (PFNENUMPRINTERS)GetProcAddress(hmodWinspool, "EnumPrintersW");
    g_pfnEnumJobs     = (PFNENUMJOBS)    GetProcAddress(hmodWinspool, "EnumJobsW");
    g_pfnOpenPrinter  = (PFNOPENPRINTER) GetProcAddress(hmodWinspool, "OpenPrinterW");
#else
    g_pfnEnumPrinters = (PFNENUMPRINTERS)GetProcAddress(hmodWinspool, "EnumPrintersA");
    g_pfnEnumJobs     = (PFNENUMJOBS)    GetProcAddress(hmodWinspool, "EnumJobsA");
    g_pfnOpenPrinter  = (PFNOPENPRINTER) GetProcAddress(hmodWinspool, "OpenPrinterA");
#endif
    g_pfnClosePrinter = (PFNCLOSEPRINTER)GetProcAddress(hmodWinspool, "ClosePrinter");

    if (!g_pfnEnumPrinters || !g_pfnEnumJobs
        || !g_pfnOpenPrinter || !g_pfnClosePrinter)
    {
        Assert(FALSE);
        goto Error2;
    }

    // Get fixed data
    dwSize = ARRAYSIZE(szUserName);
    if (!GetUserName(szUserName, &dwSize))
    {
        // non repro bug 6853/5592 is probably caused by GetUserName failing.
        // since this bug gets reported occasionally, see if this fixes it.
        // Note: we may want to change the tip message to not include this
        // empty user name.
        DebugMsg(DM_TRACE, TEXT("sh TR - GetUserName failed!"));
        Assert(0); // why would this fail?
        szUserName[0] = TEXT('\0');
    }
    LoadString(hinstCabinet, IDS_NUMPRINTJOBS, szFormat, ARRAYSIZE(szFormat));
    aArgs[0] = (DWORD)-1;
    aArgs[1] = (DWORD)szUserName;

    // Set up initial data

    ZeroMemory(&IconData, SIZEOF(IconData));
    IconData.cbSize = SIZEOF(IconData);
    IconData.hWnd = data.hWnd;
    //IconData.uID = 0;
    IconData.uFlags = NIF_MESSAGE;
    IconData.uCallbackMessage = WMTRAY_PRINTICONNOTIFY;
    // IconData.hIcon filled in when message sent
    // IconData.szTip filled in when message sent

    dwNotifyMessage = NIM_ADD;  // next operation for the icon

    nIconShown = 0;             // icon id of printer icon currently displayed
    hIconNormal = NULL;         // handle of ICO_PRINTER
    hIconError = NULL;          // handle of ICO_PRINTER_ERROR

    haPrinterNames = NULL;      // list of currently polled printers
    psf = NULL;                 // IShellFolder of Printers Folder
    dwTimeOut = POLLINTERVAL;   // time before next poll

    nFound = 0;                 // # jobs found after most recent poll
    dwStatus = 0;               // Status after most recent poll

    // process messages

    for ( ; TRUE ; )
    {
        int iPrinter, iJob;
        JOB_INFO_1 *pJobs;
        HANDLE hPrinter;
        DWORD dwTime;  // elapsed time for this wait
        DWORD dw;      // return value from this wait

        dwTime = GetTickCount();
        dw = MsgWaitForMultipleObjects(1, &(data.heWakeUp),
                FALSE, dwTimeOut, QS_ALLINPUT);
        dwTime = GetTickCount() - dwTime;

        switch (dw)
        {
        case WAIT_OBJECT_0:
        case WAIT_FAILED:
            // the tray is shutting down
            DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Thread_PrinterPoll shutting down"));
            goto exit;

        case WAIT_TIMEOUT:

#ifndef WINNT
            if (g_ts.uAutoHide & AH_HIDING)
            {
                // don't poll if the tray is hidden
                dwTimeOut = MINPOLLINTERVAL;
                break;
            }
#endif

            DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Thread_PrinterPoll timeout polling"));

            // we need to poll -- jump into the message loop
            pidlFree = NULL;
            goto poll;

        default:
            // we have messages
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
                switch (msg.message - WM_USER)
                {
                // NOTE: These can't (& don't) conflict w/ our SHCNE_ messages
                case WM_LBUTTONDBLCLK:
                case WM_RBUTTONUP:
                {
                    HMENU hmenu;

                    // REVIEW: why does Tray_ContextMenu do it this way?
                    // Don't we have a function to get the submenu?

                    hmenu = LoadMenu(hinstCabinet, MAKEINTRESOURCE(MENU_PRINTNOTIFYCONTEXT));
                    if (hmenu)
                    {
                        HMENU hmContext;
                        int idCmd;
                        int idDefCmd = IDM_PRINTNOTIFY_FOLDER;

                        hmContext = GetSubMenu(hmenu, 0);

                        // Append each printer we're polling
                        if (haPrinterNames)
                        {
                            MENUITEMINFO mii;
                            int i;
                            TCHAR szTemplate[60];

                            // If error, we'll need szTemplate
                            if (dwStatus & JOB_STATUS_ERROR_BITS)
                            {
                                LoadString(hinstCabinet, IDS_PRINTER_ERROR, szTemplate, ARRAYSIZE(szTemplate));
                            }

                            mii.cbSize = SIZEOF(MENUITEMINFO);
                            mii.fMask = MIIM_TYPE | MIIM_ID;
                            mii.fType = MF_STRING;

                            InsertMenu(hmContext, (UINT)-1, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);

                            for (i = DSA_GetItemCount(haPrinterNames) - 1 ;
                                 i >= 0 ; --i)
                            {
                                LPPRINTERNAME pPrinter;
                                TCHAR szBuf[MAXPRINTERBUFFER+60];
                                LPTSTR pName;

                                pPrinter = DSA_GetItemPtr(haPrinterNames,i);

                                // give it an id
                                mii.wID = i+IDM_PRINTNOTIFY_FOLDER+1;

                                // indicate error state via name
                                pName = pPrinter->szPrinterName;
                                if (pPrinter->fInErrorState)
                                {
                                    wsprintf(szBuf, szTemplate, pName);
                                    pName = szBuf;

                                    // default to first error state printer
                                    if (idDefCmd == IDM_PRINTNOTIFY_FOLDER)
                                        idDefCmd = mii.wID;
                                }

                                // Name of the printer
                                mii.dwTypeData = pName;
                                mii.cch = lstrlen(pName);

                                InsertMenuItem(hmContext, (UINT)-1, MF_BYPOSITION, &mii);
                            }
                        } // if (haPrinterNames)

                        // show the context menu
                        if (msg.message == WM_USER + WM_RBUTTONUP)
                        {
                            HWND hwndOwner;
                            DWORD dwPos;

                            // We need an hwnd owned by this thread.
                            hwndOwner = CreateWindow(c_szStatic, NULL,
                                                WS_DISABLED, 0, 0, 0, 0,
                                                NULL, NULL, hinstCabinet, 0L);
                            dwPos = GetMessagePos();
                            SetMenuDefaultItem(hmContext, idDefCmd, MF_BYCOMMAND);
                            SetForegroundWindow(hwndOwner);
                            SetFocus(hwndOwner);
                            idCmd = TrackPopupMenu(hmContext,
                                TPM_RETURNCMD | TPM_RIGHTBUTTON | TPM_LEFTALIGN,
                                LOWORD(dwPos), HIWORD(dwPos), 0, hwndOwner, NULL);
                            DestroyWindow(hwndOwner);
                        }
                        else
                        {
                            idCmd = idDefCmd;
                        }

                        if (idCmd != 0)
                        {
                            int iStart, iStop;

                            if (idCmd == IDM_PRINTNOTIFY_FOLDER)
                            {
                                iStart = 0;
                                iStop = DSA_GetItemCount(haPrinterNames);
                            }
                            else
                            {
                                iStart = idCmd - IDM_PRINTNOTIFY_FOLDER - 1;
                                iStop = iStart + 1;
                            }

                            for ( ; iStart < iStop ; iStart++)
                            {
                                LPPRINTERNAME pPrinter;
                                LPCONTEXTMENU pcm;

                                pPrinter = DSA_GetItemPtr(haPrinterNames,
                                                iStart);

                                // open selected queue view
                                DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Open '%s'"), pPrinter->szPrinterName);
                                psf->lpVtbl->GetUIObjectOf(psf, NULL, 1, &pPrinter->pidlPrinter,
                                    &IID_IContextMenu, NULL, &pcm);
                                if (pcm)
                                {
                                    //
                                    //  We need to call QueryContextMenu() so that CDefFolderMenu
                                    // can initialize its dispatch table correctly.
                                    //
                                    HMENU hmenuTmp = CreatePopupMenu();
                                    if (hmenuTmp)
                                    {
                                        HRESULT hres;

                                        hres = pcm->lpVtbl->QueryContextMenu(pcm, hmenuTmp, 0,
                                                    0, 0x7FFF, CMF_DEFAULTONLY);
                                        if (SUCCEEDED(hres))
                                        {
                                            CMINVOKECOMMANDINFOEX ici = {
                                                SIZEOF(CMINVOKECOMMANDINFOEX),
                                                0L,
                                                NULL,
                                                (LPSTR)MAKEINTRESOURCE(GetMenuDefaultItem(hmenuTmp, MF_BYCOMMAND, 0)),
                                                NULL, NULL,
                                                SW_NORMAL,
                                            };

                                            pcm->lpVtbl->InvokeCommand(pcm,
                                                     (LPCMINVOKECOMMANDINFO)&ici);
                                        }

                                        DestroyMenu(hmenuTmp);
                                    }

                                    pcm->lpVtbl->Release(pcm);
                                }
                            }
                        }

                        DestroyMenu(hmenu);

                    } // if (hmenu)
                    break;
                }

                case SHCNE_CREATE:
                case SHCNE_DELETE:

                    DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Thread_PrinterPoll SHCNE_CREATE/DELETE"));
                    PrintNotify_AddToQueue(&haPrinterNames, &psf, (LPCITEMIDLIST)msg.lParam);

                    //
                    // We need to free the pidl passed in.
                    //
                    pidlFree = (LPITEMIDLIST)msg.lParam;
poll:
                    dwTimeOut = POLLINTERVAL;

                    // just in case we failed AddToQueue
                    if (!haPrinterNames)
                    {
                        Assert(FALSE);
                        break;
                    }

                    nFound = 0;
                    dwStatus = 0;

                    for (iPrinter=DSA_GetItemCount(haPrinterNames)-1 ;
                         iPrinter>=0 ; --iPrinter)
                    {
                        LPPRINTERNAME pPrinter;
                        BOOL fFound;
                        BOOL fNetDown;

                        pPrinter = DSA_GetItemPtr(haPrinterNames,iPrinter);

                        // if the tray is shutting down while we're in the
                        // middle of polling, we might want to exit this loop

                        if (!g_pfnOpenPrinter(pPrinter->szPrinterName, &hPrinter, NULL))
                        {
                            // Can't open the printer?  Remove it so we don't keep trying
                            DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - can't open [%s]"), pPrinter->szPrinterName);
                            goto remove_printer;
                        }

                        DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - polling [%s]"), pPrinter->szPrinterName);

                        fFound = FALSE;
                        iJob = _EnumJobs(hPrinter, 1, &pJobs) - 1;
                        fNetDown = GetLastError() == ERROR_BAD_NET_RESP;
                        pPrinter->fInErrorState = FALSE;
                        for ( ; iJob>=0 ; --iJob)
                        {
                            // if GetUserName failed, match all jobs
#ifdef WINNT
                            // Exclude jobs that are status PRINTED.
                            if ((!lstrcmpi(pJobs[iJob].pUserName, szUserName) ||
                                !szUserName[0]) &&
                                !(pJobs[iJob].Status & JOB_STATUS_PRINTED))
#else
                            if (!lstrcmpi(pJobs[iJob].pUserName, szUserName) ||
                                !szUserName[0])
#endif
                            {
                                // We found a job owned by the user,
                                // so continue to show the icon
                                ++nFound;
                                dwStatus |= pJobs[iJob].Status;
                                if (pJobs[iJob].Status & JOB_STATUS_ERROR_BITS)
                                    pPrinter->fInErrorState = TRUE;
                                fFound = TRUE;
                            }
                        }

                        g_pfnClosePrinter(hPrinter);

                        if (pJobs)
                        {
                            Free(pJobs);
                        }

                        // there may be local jobs (fFound), but if the net is down
                        // then we don't want to poll this printer again -- we have to
                        // wait for the net to timeout which is SLOW.
                        if (!fFound || fNetDown)
                        {
#ifdef DEBUG
                            if (fNetDown)
                                DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - net is down for [%s]"), pPrinter->szPrinterName);
                            else
                                DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - no jobs for [%s]"), pPrinter->szPrinterName);
#endif
remove_printer:
                            // no need to poll this printer any more
                            ILFree(pPrinter->pidlPrinter);
                            DSA_DeleteItem(haPrinterNames, iPrinter);
                        }
                    } // polling loop

#ifndef WINNT
update_icon:
#endif
                    if (nFound)
                    {
                        UINT nIcon = (dwStatus & JOB_STATUS_ERROR_BITS) ?
                                        ICO_PRINTER_ERROR : ICO_PRINTER;

                        // Keep track of small icon sizes so we can redraw the
                        // icon whenever this changes.
                        cxSmIcon = GetSystemMetrics(SM_CXSMICON);
                        cySmIcon = GetSystemMetrics(SM_CYSMICON);

                        if ((nIconShown != nIcon) || cxSmIcon != s_cxSmIcon || cySmIcon != s_cySmIcon)
                        {
                            nIconShown = nIcon;

                            s_cxSmIcon = GetSystemMetrics(SM_CXSMICON);
                            s_cySmIcon = GetSystemMetrics(SM_CYSMICON);

                            // show an error printer icon if the print job is in
                            // an error state which "requires user intervention"
                            if (dwStatus & JOB_STATUS_ERROR_BITS)
                            {
                                if (hIconError == NULL)
                                    hIconError = (HICON)LoadImage(hinstCabinet,
                                        MAKEINTRESOURCE(nIcon), IMAGE_ICON,
                                        GetSystemMetrics(SM_CXSMICON),
                                        GetSystemMetrics(SM_CYSMICON), 0);
                                IconData.hIcon = hIconError;
                            }
                            else
                            {
                                if (hIconNormal == NULL)
                                    hIconNormal = (HICON)LoadImage(hinstCabinet,
                                        MAKEINTRESOURCE(nIcon), IMAGE_ICON,
                                        GetSystemMetrics(SM_CXSMICON),
                                        GetSystemMetrics(SM_CYSMICON), 0);
                                IconData.hIcon = hIconNormal;
                            }

                            IconData.uFlags |= NIF_ICON;
                        }
                    }
                    else
                    {
                        if (pidlFree)
                        {
                            ILGlobalFree(pidlFree);
                            pidlFree = NULL;
                        }
                        goto exit;
                    }

                    if (aArgs[0] != (DWORD)nFound)
                    {
                        aArgs[0] = nFound;

                        if (!FormatMessage(
                                FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ARGUMENT_ARRAY,
                                szFormat, 0, 0, IconData.szTip, ARRAYSIZE(IconData.szTip),
                                (va_list*)aArgs))
                        {
                            IconData.szTip[0] = TEXT('\0');
                        }

                        IconData.uFlags |= NIF_TIP;
                    }

                    // if the state has changed, update the tray notification area
                    if (IconData.uFlags)
                    {
                        DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - updating icon area"));
                        Shell_NotifyIcon(dwNotifyMessage, &IconData);
                        dwNotifyMessage = NIM_MODIFY;
                        IconData.uFlags = 0;
                    }
                    break;

#ifndef WINNT
                //
                // WINNT is hacked only to send SHCNE_CREATE messages.
                //
                case SHCNE_UPDATEITEM:

                    //
                    // We need to free the pidl if one is passed in.
                    // (Currently one is not.)
                    //
                    pidlFree = (LPITEMIDLIST)msg.lParam;

                    DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Thread_PrinterPoll SHCNE_UPDATEITEM"));

                    // if the JOB_STATUS_ERROR_BITS has
                    // changed, update the printer icon
                    if (((DWORD)msg.wParam ^ dwStatus) & JOB_STATUS_ERROR_BITS)
                    {
                        if ((DWORD)msg.wParam & JOB_STATUS_ERROR_BITS)
                        {
                            // A job just went into the error state, turn the
                            // icon on right away.
                            dwStatus |= JOB_STATUS_ERROR_BITS;
                            goto update_icon;
                        }
                        else
                        {
                            // Maybe the last job in an error state went
                            // out of the error state. Poll to find out.
                            // (But not more frequently than MINPOLLINTERVAL.)
                            if (dwTimeOut - dwTime < POLLINTERVAL - MINPOLLINTERVAL)
                                goto poll;
                        }
                    }

                    // Or if the icon size has changed.
                    if (s_cxSmIcon != cxSmIcon || s_cySmIcon != cySmIcon)
                        goto update_icon;

                    // don't wait as long next time
                    dwTimeOut -= dwTime;
                    if (dwTimeOut > POLLINTERVAL)
                    {
                        // oops, rollover; time to poll
                        goto poll;
                    }

                    break;
#endif
#ifdef DEBUG
                default:
                    DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Bogus PeekMessage 0x%X in Thread_PrinterPoll"), msg.message);
                    break;
#endif
                }

                if (pidlFree)
                {
                    ILGlobalFree(pidlFree);
                    pidlFree = NULL;
                }

            } // while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        } // switch (dw)
    } // for ( ; TRUE ; )

exit:
Error2:
    FreeLibrary(hmodWinspool);
Error1:
    SFCTerminateThread();
Error0:
    {
        HANDLE htPrinterPoll;

        // Set g_ts.htPrinterPoll to null here and only here. we have a timing
        // bug where someone sets this to null and we hang.
        // Because g_ts.htPrinterPoll was null, we couldn't trace down what
        // happened to the polling thread (it was lost).
        ENTERCRITICAL;
        htPrinterPoll = g_ts.htPrinterPoll;
        g_ts.htPrinterPoll = NULL;
        LEAVECRITICAL;

        Assert(htPrinterPoll);
        CloseHandle(htPrinterPoll);
    }

    if (psf)
    {
        psf->lpVtbl->Release(psf);
    }

    if (haPrinterNames)
    {
        int i;

        for (i = DSA_GetItemCount(haPrinterNames) - 1 ; i >= 0 ; i--)
        {
            LPPRINTERNAME pPrinter = DSA_GetItemPtr(haPrinterNames,i);
            ILFree(pPrinter->pidlPrinter);
        }

        DSA_Destroy(haPrinterNames);
    }

    if (nIconShown)
    {
        Shell_NotifyIcon(NIM_DELETE, &IconData);
    }

    if (hIconNormal)
    {
        DestroyIcon(hIconNormal);
    }
    if (hIconError)
    {
        DestroyIcon(hIconError);
    }

    return(0);
}


// This functions adds the printer name pointed to by pidl to the list
// of printers to poll by Thread_PrinterPoll
void PrintNotify_AddToQueue(LPHANDLE phaPrinterNames, LPSHELLFOLDER *ppsf, LPCITEMIDLIST pidlPrinter)
{
    STRRET strret;
    LPCTSTR pNewPrinter;
    LPPRINTERNAME pPrinter;
    int i;
    LPOneTreeNode pnode;
    LPSHELLFOLDER psf;
    PRINTERNAME pn;

#ifdef UNICODE
    LPCTSTR pszFree = NULL;
#endif

    // We need the IShellFolder for the Printers Folder in order to get
    // the display name of the printer
    psf = *ppsf;
    if (psf == NULL)
    {
        pnode = OTGetNodeFromIDList(g_ts.pidlPrintersFolder, OTGNF_TRYADD | OTGNF_VALIDATE);
        if (!pnode)
        {
            goto exit;
        }
        psf = OTBindToFolder(pnode);
        OTRelease(pnode);
        if (!psf)
        {
            goto exit;
        }
        *ppsf = psf;
    }

    // all this work to get the name of the printer:
    psf->lpVtbl->GetDisplayNameOf(psf, pidlPrinter, SHGDN_FORPARSING, &strret);

    // we know how we implemented the printers GetDisplayNameOf function
#ifdef UNICODE

    //
    // pszFree saves a copy that will be freed on exit.
    //
    Assert(strret.uType == STRRET_OLESTR);
    pszFree = pNewPrinter = strret.pOleStr;
#else
    Assert(strret.uType == STRRET_OFFSET);
    pNewPrinter = STRRET_OFFPTR(pidlPrinter, &strret);
#endif

    if (*phaPrinterNames == NULL)
    {
        *phaPrinterNames = DSA_Create(SIZEOF(PRINTERNAME), 4);
        if (*phaPrinterNames == NULL)
            goto exit;
    }

    for (i=DSA_GetItemCount(*phaPrinterNames)-1 ; i>=0 ; --i)
    {
        pPrinter = DSA_GetItemPtr(*phaPrinterNames,i);

        if (!lstrcmp(pPrinter->szPrinterName, pNewPrinter))
        {
            // printer already in list, no need to add it
            DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - [%s] already in poll list"), pNewPrinter);
            goto exit;
        }
    }

    DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - adding [%s] to poll list"), pNewPrinter);

    lstrcpy(pn.szPrinterName, pNewPrinter);
    pn.fInErrorState = FALSE;
    pn.pidlPrinter = ILClone(pidlPrinter);
    DSA_InsertItem(*phaPrinterNames, 0x7fff, &pn);

exit:

#ifdef UNICODE
    //
    // Free the allocated string from GetDisplayNameOf in strret.
    //
    if (pszFree)
    {
        SHFree((LPVOID)pszFree);
    }
#endif
    return;
}


BOOL PrintNotify_StartThread(HWND hWnd)
{
    PPRINTERPOLLDATA pPrinterPollData;
    BOOL fRet;

    if (g_ts.htPrinterPoll)
    {
        return TRUE;
    }

    ENTERCRITICAL;

    // We check this again so that the above check will go very quickly,
    // and almost always do the return

    fRet = g_ts.htPrinterPoll != NULL;
    if (fRet)
    {
        goto Error_Exit;
    }

    // We keep one event around to communicate with the polling thread so
    // we don't need to keep re-creating it whenever the user prints.
    // On the down side: this thing never does get freed up... Oh well.
    if (!g_ts.heWakeUp)
    {
        g_ts.heWakeUp = CreateEvent(NULL, FALSE, FALSE, NULL);
        if (!g_ts.heWakeUp)
            goto Error_Exit;
    }

    pPrinterPollData = (PPRINTERPOLLDATA)GlobalAlloc(GPTR, SIZEOF(PRINTERPOLLDATA));
    if (!pPrinterPollData)
    {
        goto Error_Exit;
    }
    pPrinterPollData->hWnd = hWnd;
    pPrinterPollData->heWakeUp = g_ts.heWakeUp;

    g_ts.htPrinterPoll = CreateThread(NULL, 0, Thread_PrinterPoll,
                        pPrinterPollData, 0, &g_ts.idPrinterPoll);

    fRet = g_ts.htPrinterPoll != NULL;

    LEAVECRITICAL;

    if (fRet)
    {
        // wait until the new thread's message queue is ready
        // I had INFINITE here, but something funky happened once on a build
        // machine which caused MSGSRV32 to hang waiting for this thread to
        // unblock. If we timeout, the worst that will happen is our icon won't
        // show and/or we'll keep destroying re-creating the polling thread
        // until everything works.
        WaitForSingleObject(g_ts.heWakeUp, ERRTIMEOUT);
        DebugMsg(DM_TRACE, TEXT("sh PRINTNOTIFY - Thread_PrinterPoll started"));
    }
    else
    {
        GlobalFree(pPrinterPollData);
    }

    goto Exit;

Error_Exit:
    LEAVECRITICAL;
Exit:
    return fRet;
}

void PrintNotify_HandleFSNotify(HWND hWnd, LPCITEMIDLIST *ppidl, LONG lEvent)
{
    LPITEMIDLIST pidl;

    // Look at the pidl to determine if it's a Print Job notification. If it
    // is, we may also need a copy of the relative Printer pidl. Pass the
    // notification on to our polling thread so the tray isn't stuck waiting
    // for a (net) print subsystem query (up to 15 seconds if net is funky).

    pidl = ILClone(ppidl[0]);
    if (pidl)
    {
        LPITEMIDLIST pidlPrintJob;
        LPITEMIDLIST pidlPrinter;
        LPITEMIDLIST pidlPrintersFolder;
        USHORT cbPidlPrinter;

        pidlPrintJob = ILFindLastID(pidl);
        ILRemoveLastID(pidl);
        pidlPrinter = ILFindLastID(pidl);
        cbPidlPrinter = pidlPrinter->mkid.cb;
        ILRemoveLastID(pidl);
        pidlPrintersFolder = pidl;

        if (ILIsEqual(pidlPrintersFolder, g_ts.pidlPrintersFolder))
        {
            if (PrintNotify_StartThread(hWnd))
            {
                LPCITEMIDLIST pidlPrinterName = NULL;
                LPSHCNF_PRINTJOB_DATA pData;

                // HACK: we know the format of this (internal) "temporary" pidl
                // which exists only between SHChangeNotify and here
                pData = (LPSHCNF_PRINTJOB_DATA)(pidlPrintJob->mkid.abID);

                // PERFORMANCE: SHCNE_CREATE can just add one to the local count
                // and SHCNE_DELETE will sub one from the local count and start
                // polling this printer to get the net count.
                // GOOD ENOUGH: poll on create and delete.
                if (SHCNE_UPDATEITEM != lEvent)
                {
                    // this was whacked with one of the above ILRemoveLastID()s
                    pidlPrinter->mkid.cb = cbPidlPrinter;

                    // this is freed in the polling thread
                    pidlPrinterName = ILGlobalClone(pidlPrinter);
                }

                // REVIEW: What if we PostThreadMessage to a dead thread?
                // PrintNotify_StartThread can start the thread and give us
                // the idPrinterPoll, but then under certain error conditions
                // the new thread will terminate itself. This can happen
                // after the above if and before this post.

                PostThreadMessage(g_ts.idPrinterPoll, WM_USER+(UINT)lEvent,
                        (WPARAM)pData->Status, (LPARAM)pidlPrinterName);
            }
        }

        ILFree(pidl);
    }
}

void PrintNotify_Exit(void)
{
    // PrintNotify_Init only initializes if it can get the pidlPrintsFolder
    if (g_ts.pidlPrintersFolder)
    {
        if (g_ts.uPrintNotify)
        {
            SHChangeNotifyDeregister(g_ts.uPrintNotify);
        }

        if (g_ts.htPrinterPoll)
        {
            // Signal the PrinterPoll thread to exit
            if (g_ts.heWakeUp)
            {
                SetEvent(g_ts.heWakeUp);
            }

            WaitForSingleObject(g_ts.htPrinterPoll, ERRTIMEOUT);
        }

        // Free this last just in case the htPrinterPoll thread was active
        ILFree((LPITEMIDLIST)g_ts.pidlPrintersFolder);
    }
}


void PrintNotify_IconNotify(HWND hWnd, UINT uMsg)
{
    switch (uMsg)
    {
        case WM_LBUTTONDBLCLK:
        case WM_RBUTTONUP:
            // call off to worker thread to bring up the context menu --
            // we need to access the list of polling printers for this
            if (g_ts.htPrinterPoll && g_ts.idPrinterPoll)
            {
                PostThreadMessage(g_ts.idPrinterPoll, WM_USER+uMsg, 0, 0);
            }
            else if (uMsg == WM_LBUTTONDBLCLK)
            {
                ShowFolder(hWnd, IDM_PRINTERS, COF_USEOPENSETTINGS);
            }
            break;
    }
}

////////////////////////////////////////////////////////////////////////////
// End Print Notify stuff
////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////
//
// IsDisplayChangeSafe()
//
// make sure the display change is a safe thing to do.
//
// NOTE this code can easily be fooled if we are running on HW that will be
// gone after a undoc.
//
////////////////////////////////////////////////////////////////////////////

BOOL IsDisplayChangeSafe()
{
    HDC  hdc;
    BOOL fSafe;
    BOOL fVGA;

    hdc = GetDC(NULL);

    fVGA = GetDeviceCaps(hdc, PLANES) == 4 &&
           GetDeviceCaps(hdc, BITSPIXEL) == 1 &&
           GetDeviceCaps(hdc, HORZRES) == 640 &&
           GetDeviceCaps(hdc, VERTRES) == 480;

    fSafe = fVGA || (GetDeviceCaps(hdc, CAPS1) & C1_REINIT_ABLE);

    ReleaseDC(NULL, hdc);
    return fSafe;
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

int BlueScreenMesssgeBox(TCHAR *szTitle, TCHAR *szText, int flags)
{
#ifndef WINNT
    BLUESCREENINFO bsi = {szText, szTitle, flags};
    int cb;
    HANDLE h;

//BUGBUG
//  AnsiToOEM(szTitle, szTitle);
//  AnsiToOEM(szText, szText);

    h = CreateFile(SHELLFILENAME, GENERIC_WRITE, FILE_SHARE_WRITE,
            0, OPEN_EXISTING, 0, 0);

    if (h != INVALID_HANDLE_VALUE)
    {
        DeviceIoControl(h, WSHIOCTL_BLUESCREEN, &bsi, SIZEOF(bsi),
            &flags, SIZEOF(flags), &cb, 0);
        CloseHandle(h);
    }
    return flags;
#else
    // BUGBUG - We need to figure out what to do for NT here
    return 0;
#endif
}

const TCHAR c_szConfig[] = REGSTR_KEY_CONFIG;
const TCHAR c_szSlashDisplaySettings[] = TEXT("\\") REGSTR_PATH_DISPLAYSETTINGS;
const TCHAR c_szResolution[] = REGSTR_VAL_RESOLUTION;

//
// GetMinDisplayRes
//
// walk all the configs and find the minimum display resolution.
//
// when doing a hot undock we have no idea what config we are
// going to undock into.
//
// we want to put the display into a "common" mode that all configs
// can handle so we dont "fry" the display when we wake up in the
// new mode.
//
DWORD GetMinDisplayRes(void)
{
    TCHAR ach[128];
    UINT cb;
    HKEY hkey;
    HKEY hkeyT;
    int i,n;
    int xres=0;
    int yres=0;

    if (RegOpenKey(HKEY_LOCAL_MACHINE, c_szConfig, &hkey) == ERROR_SUCCESS)
    {
        for (n=0; RegEnumKey(hkey, n, ach, ARRAYSIZE(ach)) == ERROR_SUCCESS; n++)
        {
            lstrcat(ach,c_szSlashDisplaySettings);  // 0000\Display\Settings

            DebugMsg(DM_TRACE, TEXT("GetMinDisplayRes: found config %s"), ach);

            if (RegOpenKey(hkey, ach, &hkeyT) == ERROR_SUCCESS)
            {
                cb = SIZEOF(ach);
                ach[0] = 0;
                RegQueryValueEx(hkeyT, c_szResolution, 0, NULL, (LPBYTE) &ach[0], &cb);

                DebugMsg(DM_TRACE, TEXT("GetMinDisplayRes: found res %s"), ach);

                if (ach[0])
                {
                    i = StrToInt(ach);

                    if (i < xres || xres == 0)
                        xres = i;

                    for (i=1;ach[i] && ach[i-1]!=TEXT(','); i++)
                        ;

                    i = StrToInt(ach + i);

                    if (i < yres || yres == 0)
                        yres = i;
                }
                else
                {
                    xres = 640;
                    yres = 480;
                }

                RegCloseKey(hkeyT);
            }
        }
        RegCloseKey(hkey);
    }

    DebugMsg(DM_TRACE, TEXT("GetMinDisplayRes: xres=%d yres=%d"), xres, yres);

    if (xres == 0 || yres == 0)
        return MAKELONG(640,480);
    else
        return MAKELONG(xres,yres);
}

//
//  the user has done a un-doc or re-doc we may need to switch
//  to a new display mode.
//
//  if fCritical is set the mode switch is critical, show a error
//  if it does not work.
//
void HandleDisplayChange(int x, int y, BOOL fCritical)
{
    TCHAR ach[256];
    DEVMODE dm;
    LONG err;
    HDC hdc;

    //
    //  try to change into the mode specific to this config
    //  HKEY_CURRENT_CONFIG has already been updated by PnP
    //  so all we have to do is re-init the current display
    //
    //  we cant default to current bpp because we may have changed configs.
    //  and the bpp may be different in the new config.
    //
    dm.dmSize   = SIZEOF(dm);
    dm.dmFields = DM_BITSPERPEL;

    hdc = GetDC(NULL);
    dm.dmBitsPerPel = GetDeviceCaps(hdc, PLANES) * GetDeviceCaps(hdc, BITSPIXEL);
    ReleaseDC(NULL, hdc);

    if (x + y)
    {
        dm.dmFields    |= DM_PELSWIDTH|DM_PELSHEIGHT;
        dm.dmPelsWidth  = x;
        dm.dmPelsHeight = y;
    }

    err = ChangeDisplaySettings(&dm, 0);

    if (err != 0 && fCritical)
    {
        //
        //  if it fails make a panic atempt to try 640x480, if
        //  that fails also we should put up a big error message
        //  in text mode and tell the user he is screwed.
        //
        dm.dmFields     = DM_PELSWIDTH|DM_PELSHEIGHT|DM_BITSPERPEL;
        dm.dmPelsWidth  = 640;
        dm.dmPelsHeight = 480;

        err = ChangeDisplaySettings(&dm, 0);

        if (err != 0)
        {
            //
            //  if 640x480 fails we should put up a big error message
            //  in text mode and tell the user he is screwed. and
            //  offer to reboot his machine.
            //
            MessageBeep(0);

            LoadString(hinstCabinet, IDS_DISPLAY_ERROR, ach, ARRAYSIZE(ach));
            BlueScreenMesssgeBox(NULL, ach, MB_OK);
        }
    }
}


void HandleGlobalHotkey(WPARAM wParam)
{
    PFileCabinet pfc = g_pfcTray;
    switch(wParam)
    {
    case GHID_RUN:
        Tray_RunDlg();
        break;

    case GHID_MINIMIZEALL:
        if (CanMinimizeAll(pfc->hwndView))
            MinimizeAll(pfc->hwndView);
        SetForegroundWindow(v_hwndDesktop);
        break;

    case GHID_UNMINIMIZEALL:
        RestoreWindowPositions();
        break;

    case GHID_HELP:
        TrayCommand(pfc, IDM_HELPSEARCH);
        break;

    case GHID_EXPLORER:
        ShowFolder(pfc->hwndMain, IDM_MYCOMPUTER, COF_CREATENEWWINDOW | COF_EXPLORE);
        break;

    case GHID_FINDFILES:
        TrayCommand(pfc, FCIDM_FINDFILES);
        break;

    case GHID_FINDCOMPUTER:
        TrayCommand(pfc, FCIDM_FINDCOMPUTER);
        break;

    case GHID_TASKTAB:
    case GHID_TASKSHIFTTAB:
        if (GetForegroundWindow() != v_hwndTray)
            SetForegroundWindow(v_hwndTray);
        SendMessage(pfc->hwndView, TM_TASKTAB, wParam == GHID_TASKTAB ? 1 : -1, 0L);
        break;

    case GHID_SYSPROPERTIES:
#define IDS_SYSDMCPL            0x2334  // from shelldll
        SHRunControlPanel(MAKEINTRESOURCE(IDS_SYSDMCPL), pfc->hwndMain);
        break;
    }
}

void UnregisterGlobalHotkeys()
{
    int i;

    for (i = 0 ; i < GHID_MAX; i++) {
        UnregisterHotKey(v_hwndDesktop, i);
    };
}

void RegisterGlobalHotkeys()
{
    int i;

    for (i = 0 ; i < GHID_MAX; i++) {
        RegisterHotKey(v_hwndDesktop, i, HIWORD(GlobalKeylist[i]), LOWORD(GlobalKeylist[i]));
    };
}