/*******************************************************************************
*
*  (C) COPYRIGHT MICROSOFT CORP., 1993-1994
*
*  TITLE:       VOLUME.C
*
*  VERSION:     1.0
*
*  AUTHOR:      RAL
*
*  DATE:        11/01/94
*
********************************************************************************
*
*  CHANGE LOG:
*
*  DATE        REV DESCRIPTION
*  ----------- --- -------------------------------------------------------------
*  Nov. 11, 94 RAL Original
*  Oct. 24, 95 Shawnb UNICODE enabled
*
*******************************************************************************/
#include "stdafx.h"
#include "systray.h"

#include <objbase.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#include <initguid.h>
#include <devguid.h>

#include <ks.h>
#include <ksmedia.h>
#include <mmddkp.h>


#define HWAVEOUT_MAPPER       ((HWAVEOUT)IntToPtr(WAVE_MAPPER))
#define HWAVEIN_MAPPER        ((HWAVEIN)IntToPtr(WAVE_MAPPER))
#define HMIDIOUT_MAPPER       ((HMIDIOUT)IntToPtr(WAVE_MAPPER))

#define HWAVEOUT_INDEX(i)     ((HWAVEOUT)IntToPtr(i))
#define HWAVEIN_INDEX(i)      ((HWAVEIN)IntToPtr(i))
#define HMIDIOUT_INDEX(i)     ((HMIDIOUT)IntToPtr(i))
#define HMIXER_INDEX(i)       ((HMIXER)IntToPtr(i))
#define HMIXEROBJ_INDEX(i)    ((HMIXEROBJ)IntToPtr(i))

/* defined in mmddk.h */
#define DRV_QUERYDEVNODE     (DRV_RESERVED + 2)

#define VOLUMEMENU_PROPERTIES               100
#define VOLUMEMENU_SNDVOL                   101

extern HINSTANCE g_hInstance;

static BOOL    g_bVolumeEnabled = FALSE;
static BOOL    g_bVolumeIconShown = FALSE;
static HICON   g_hVolumeIcon = NULL;
static HICON   g_hMuteIcon = NULL;
static HMENU   g_hVolumeMenu = NULL;
static HMIXER  g_hMixer = NULL;
static UINT    g_uMixer = 0;
static DWORD   g_dwMixerDevNode = 0;
static DWORD   g_dwMute = (DWORD) -1;
static DWORD   g_dwVSlider = 0;
static DWORD   g_dwMasterLine = (DWORD) -1;

HDEVNOTIFY DeviceEventContext = NULL;


void Volume_DeviceChange_Init(HWND hWnd, DWORD dwMixerID);
void Volume_DeviceChange_Cleanup(void);

void Volume_UpdateStatus(HWND hWnd, BOOL bShowIcon, BOOL bKillSndVol32);
void Volume_VolumeControl();
void Volume_ControlPanel(HWND hwnd);
MMRESULT Volume_GetDefaultMixerID(int *pid);
void Volume_UpdateIcon(HWND hwnd, DWORD message);
BOOL Volume_Controls(UINT uMxID);
BOOL FileExists (LPCTSTR pszFileName);
BOOL FindSystemFile (LPCTSTR pszFileName, LPTSTR pszFullPath, UINT cchSize);
void Volume_WakeUpOrClose(BOOL fClose);

HMENU Volume_CreateMenu()
{
        HMENU  hmenu;
        LPTSTR lpszMenu1;
        LPTSTR lpszMenu2;

        lpszMenu1 = LoadDynamicString(IDS_VOLUMEMENU1);
        if (!lpszMenu1)
                return NULL;

        lpszMenu2 = LoadDynamicString(IDS_VOLUMEMENU2);
        if (!lpszMenu2)
        {
                DeleteDynamicString(lpszMenu1);
                return NULL;
        }

        hmenu = CreatePopupMenu();
        if (!hmenu)
        {
                DeleteDynamicString(lpszMenu1);
                DeleteDynamicString(lpszMenu2);
                return NULL;
        }

        AppendMenu(hmenu,MF_STRING,VOLUMEMENU_SNDVOL,lpszMenu2);
        AppendMenu(hmenu,MF_STRING,VOLUMEMENU_PROPERTIES,lpszMenu1);

        SetMenuDefaultItem(hmenu,VOLUMEMENU_SNDVOL,FALSE);

        DeleteDynamicString(lpszMenu1);
        DeleteDynamicString(lpszMenu2);

        return hmenu;
}





BOOL Volume_Init(HWND hWnd)
{
        UINT        uMxID;
        const TCHAR szVolApp[] = TEXT ("SNDVOL32.EXE");

        if (g_hMixer == NULL)
        {
                if (Volume_GetDefaultMixerID(&uMxID) != MMSYSERR_NOERROR)
                        return FALSE;

                //
                // check for sndvol32 existence.  checking for the .exe
                // first will ensure that the service gets disabled properly
                //
                
                if (! FindSystemFile (szVolApp, NULL, 0))
                {
                        //
                        // disable the volume service
                        //
                        EnableService (STSERVICE_VOLUME, FALSE);
                
                        return FALSE;
                }


                //
                // do we have output volume controls on this mixer?
                //
                if (! Volume_Controls(uMxID))
                        return FALSE;

                if (mixerOpen(&g_hMixer, uMxID, (DWORD_PTR)hWnd, 0
                                , CALLBACK_WINDOW | MIXER_OBJECTF_MIXER)
                        == MMSYSERR_NOERROR)
                {
            Volume_DeviceChange_Init(hWnd, uMxID);

                        g_uMixer = uMxID;
                        if (mixerMessage (HMIXER_INDEX(uMxID), DRV_QUERYDEVNODE
                                 , (DWORD_PTR)&g_dwMixerDevNode, 0L))
                                g_dwMixerDevNode = 0L;
                        return TRUE;
                }
        }
        else
                return TRUE;

        return FALSE;
}

//
//  Called at init time and whenever services are enabled/disabled.
//  Returns false if mixer services are not active.
//
BOOL Volume_CheckEnable(HWND hWnd, BOOL bSvcEnabled)
{
        BOOL bEnable = bSvcEnabled && Volume_Init(hWnd);

        if (bEnable != g_bVolumeEnabled) {
                //
                // state change
                //
                g_bVolumeEnabled = bEnable;
                Volume_UpdateStatus(hWnd, bEnable, TRUE);
        }
        return(bEnable);
}

void Volume_UpdateStatus(HWND hWnd, BOOL bShowIcon, BOOL bKillSndVol32)
{
    // Don't show icon if not enabled
    if (!g_bVolumeEnabled)
        bShowIcon = FALSE;

        if (bShowIcon != g_bVolumeIconShown) {
                g_bVolumeIconShown = bShowIcon;
                if (bShowIcon) {
                        g_hVolumeIcon = LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_VOLUME),
                                                IMAGE_ICON, 16, 16, 0);
                        g_hMuteIcon = LoadImage(g_hInstance, MAKEINTRESOURCE(IDI_MUTE),
                                                IMAGE_ICON, 16, 16, 0);
                        Volume_UpdateIcon(hWnd, NIM_ADD);
                } else {
                        SysTray_NotifyIcon(hWnd, STWM_NOTIFYVOLUME, NIM_DELETE, NULL, NULL);
                        if (g_hVolumeIcon) {
                                DestroyIcon(g_hVolumeIcon);
                                g_hVolumeIcon = NULL;
                        }
                        if (g_hMuteIcon) {
                                DestroyIcon(g_hMuteIcon);
                                g_hMuteIcon = NULL;
                        }
                        if (g_hMixer)
                        {
                                mixerClose(g_hMixer);
                                g_hMixer = NULL;
                        }
                        g_uMixer = 0;
                        g_dwMixerDevNode = 0L;

                        //
                        // SNDVOL32 may have a TRAYMASTER window open,
                        // sitting on a timer before it closes (so multiple
                        // l-clicks on the tray icon can bring up the app
                        // quickly after the first hit).  Close that app
                        // if it's around.
                        //
                        if (bKillSndVol32)
                        {
                                Volume_WakeUpOrClose (TRUE);
                        }
                }
    }
}

const TCHAR szMapperPath[]      = TEXT ("Software\\Microsoft\\Multimedia\\Sound Mapper");
const TCHAR szPlayback[]        = TEXT ("Playback");
const TCHAR szPreferredOnly[]   = TEXT ("PreferredOnly");


/*
 * Volume_GetDefaultMixerID
 *
 * Get the default mixer id.  We only appear if there is a mixer associated
 * with the default wave.
 *
 */
MMRESULT Volume_GetDefaultMixerID(int *pid)
{
    MMRESULT        mmr;
    DWORD           dwWaveID;
    DWORD           dwMixID;
    DWORD           dwFlags = 0;
    
    mmr = waveOutMessage(HWAVEOUT_MAPPER, DRVM_MAPPER_PREFERRED_GET, (DWORD_PTR) &dwWaveID, (DWORD_PTR) &dwFlags);

    if (mmr == MMSYSERR_NOERROR)
    {
        mmr = mixerGetID(HMIXEROBJ_INDEX(dwWaveID), &dwMixID, MIXER_OBJECTF_WAVEOUT);

                if (mmr == MMSYSERR_NOERROR && pid)
                {
                        *pid = dwMixID;
                }
    }

    return mmr;
}
        

/*
 * Process line changes
 */
void Volume_LineChange(
    HWND        hwnd,
    HMIXER      hmx,
    DWORD       dwLineID)
{
    if (dwLineID != g_dwMasterLine)
                return;
    //
    // if our line is disabled, go away, I guess
    //
}

/*
 * Process control changes
 */
void Volume_ControlChange(
    HWND        hwnd,
    HMIXER      hmx,
    DWORD       dwControlID)
{
    if ((dwControlID != g_dwMute) && (g_dwMute != (DWORD) -1))
                return;

    //
    // Change mute icon state
    //
    Volume_UpdateIcon(hwnd, NIM_MODIFY);
}


BOOL Volume_IsMute()
{
    MMRESULT            mmr;
    MIXERCONTROLDETAILS mxcd;
    BOOL                fMute;

    if (!g_hMixer && (g_dwMute != (DWORD) -1))
    {
                return FALSE;
    }

    mxcd.cbStruct       = sizeof(mxcd);
    mxcd.dwControlID    = g_dwMute;
    mxcd.cChannels      = 1;
    mxcd.cMultipleItems = 0;
    mxcd.cbDetails      = sizeof(DWORD);
    mxcd.paDetails      = (LPVOID)&fMute;

    mmr = mixerGetControlDetails( (HMIXEROBJ)g_hMixer, &mxcd, MIXER_GETCONTROLDETAILSF_VALUE);

    if (mmr == MMSYSERR_NOERROR)
    {
                return fMute;
    }

    return FALSE;
}

BOOL Volume_Controls(
    UINT                uMxID)
{
    MIXERLINECONTROLS   mxlc;
    MIXERCONTROL        mxctrl;
    MIXERCAPS           mxcaps;
    MMRESULT            mmr;
    BOOL                fResult = FALSE;
    DWORD               iDest;
    g_dwMasterLine      = (DWORD) -1;
    g_dwMute            = (DWORD) -1;

    mmr = mixerGetDevCaps(uMxID, &mxcaps, sizeof(mxcaps));

    if (mmr != MMSYSERR_NOERROR)
    {
                return FALSE;
    }

    for (iDest = 0; iDest < mxcaps.cDestinations; iDest++)
    {
                MIXERLINE       mlDst;
        
                mlDst.cbStruct      = sizeof ( mlDst );
                mlDst.dwDestination = iDest;
        
                mmr = mixerGetLineInfo( HMIXEROBJ_INDEX(uMxID), &mlDst, MIXER_GETLINEINFOF_DESTINATION);

                if (mmr != MMSYSERR_NOERROR)
        {
                        continue;
        }

                switch (mlDst.dwComponentType)
                {
                    default:
                    continue;
                    
                case MIXERLINE_COMPONENTTYPE_DST_SPEAKERS:
                case MIXERLINE_COMPONENTTYPE_DST_HEADPHONES:
            {
                            g_dwMasterLine = mlDst.dwLineID;
            }
                        break;
                }
        
                mxlc.cbStruct       = sizeof(mxlc);
                mxlc.dwLineID       = g_dwMasterLine;
                mxlc.dwControlType  = MIXERCONTROL_CONTROLTYPE_MUTE;
                mxlc.cControls      = 1;
                mxlc.cbmxctrl       = sizeof(mxctrl);
                mxlc.pamxctrl       = &mxctrl;
                
                mmr = mixerGetLineControls( HMIXEROBJ_INDEX(uMxID), &mxlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);

                if (mmr == MMSYSERR_NOERROR)
        {
                        g_dwMute = mxctrl.dwControlID;
        }
        
                fResult = TRUE;
                break;
        
    }
    return fResult;
}

void Volume_UpdateIcon(
    HWND hWnd,
    DWORD message)
{
    BOOL        fMute;
    LPTSTR      lpsz;
    HICON       hVol;

    fMute   = Volume_IsMute();
    hVol    = fMute?g_hMuteIcon:g_hVolumeIcon;
    lpsz    = LoadDynamicString(fMute?IDS_MUTED:IDS_VOLUME);
    SysTray_NotifyIcon(hWnd, STWM_NOTIFYVOLUME, message, hVol, lpsz);
    DeleteDynamicString(lpsz);
}



// WinMM is telling us the preferred device has changed for some reason
// Dump the old, open the new
//
void Volume_WinMMDeviceChange(HWND hWnd)
{
    DWORD dwMixID;

        if (g_hMixer)               // Dumping the Old
        {
                mixerClose(g_hMixer);
                g_hMixer = NULL;
                g_uMixer = 0;
                g_dwMixerDevNode = 0L;
        }
                                // Opening the new
    if (Volume_GetDefaultMixerID(&dwMixID) == MMSYSERR_NOERROR)
    {   
                if ( Volume_Controls(dwMixID) && 
             (mixerOpen(&g_hMixer, dwMixID, (DWORD_PTR)hWnd, 0L, CALLBACK_WINDOW | MIXER_OBJECTF_MIXER) == MMSYSERR_NOERROR))
                {
                        Volume_UpdateStatus(hWnd, TRUE, TRUE);

                        if (mixerMessage (HMIXER_INDEX(dwMixID), DRV_QUERYDEVNODE, (DWORD_PTR)&g_dwMixerDevNode, 0L))
            {
                                g_dwMixerDevNode = 0L;
            }

                        g_uMixer = dwMixID;

            Volume_UpdateIcon(hWnd, NIM_MODIFY);
                }
                else
                {
                        Volume_UpdateStatus(hWnd, FALSE, TRUE);
                }
    }
    else
    {
                Volume_UpdateStatus(hWnd, FALSE, TRUE);
    }
}


// Need to free up in the event of a power broadcast as well
//
void Volume_HandlePowerBroadcast(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    switch (wParam)
    {
            case PBT_APMQUERYSUSPEND:
        {
                if (g_hMixer)               // Dumping the Old
                {
                        mixerClose(g_hMixer);
                        g_hMixer = NULL;
                        g_uMixer = 0;
                        g_dwMixerDevNode = 0L;
                }
        }
            break;

            case PBT_APMQUERYSUSPENDFAILED:
            case PBT_APMRESUMESUSPEND:
        {
            Volume_WinMMDeviceChange(hWnd); 
        }
            break;
    }
}


void Volume_DeviceChange_Cleanup()
{
   if (DeviceEventContext) 
   {
       UnregisterDeviceNotification(DeviceEventContext);
       DeviceEventContext = 0;
   }

   return;
}

/*
**************************************************************************************************
        Volume_GetDeviceHandle()

        given a mixerID this functions opens its corresponding device handle. This handle can be used 
        to register for DeviceNotifications.

        dwMixerID -- The mixer ID
        phDevice -- a pointer to a handle. This pointer will hold the handle value if the function is
                                successful
        
        return values -- If the handle could be obtained successfully the return vlaue is TRUE.

**************************************************************************************************
*/
BOOL Volume_GetDeviceHandle(DWORD dwMixerID, HANDLE *phDevice)
{
        MMRESULT mmr;
        ULONG cbSize=0;
        TCHAR *szInterfaceName=NULL;

        //Query for the Device interface name
        mmr = mixerMessage(HMIXER_INDEX(dwMixerID), DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&cbSize, 0L);
        if(MMSYSERR_NOERROR == mmr)
        {
                szInterfaceName = (TCHAR *)GlobalAllocPtr(GHND, (cbSize+1)*sizeof(TCHAR));
                if(!szInterfaceName)
                {
                        return FALSE;
                }

                mmr = mixerMessage(HMIXER_INDEX(dwMixerID), DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)szInterfaceName, cbSize);
                if(MMSYSERR_NOERROR != mmr)
                {
                        GlobalFreePtr(szInterfaceName);
                        return FALSE;
                }
        }
        else
        {
                return FALSE;
        }

        //Get an handle on the device interface name.
        *phDevice = CreateFile(szInterfaceName, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
                                                 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

        GlobalFreePtr(szInterfaceName);
        if(INVALID_HANDLE_VALUE == *phDevice)
        {
                return FALSE;
        }

        return TRUE;
}


/*      DeviceChange_Init()
*       First time initialization for WM_DEVICECHANGE messages
*       
*       On NT 5.0, you have to register for device notification
*/
void Volume_DeviceChange_Init(HWND hWnd, DWORD dwMixerID)
{
        DEV_BROADCAST_HANDLE DevBrodHandle;
        HANDLE hMixerDevice=NULL;


        //If we had registered already for device notifications, unregister ourselves.
        Volume_DeviceChange_Cleanup();

        //If we get the device handle register for device notifications on it.
        if(Volume_GetDeviceHandle(dwMixerID, &hMixerDevice))
        {
                memset(&DevBrodHandle, 0, sizeof(DEV_BROADCAST_HANDLE));

                DevBrodHandle.dbch_size = sizeof(DEV_BROADCAST_HANDLE);
                DevBrodHandle.dbch_devicetype = DBT_DEVTYP_HANDLE;
                DevBrodHandle.dbch_handle = hMixerDevice;

                DeviceEventContext = RegisterDeviceNotification(hWnd, &DevBrodHandle, DEVICE_NOTIFY_WINDOW_HANDLE);

                if(hMixerDevice)
                {
                        CloseHandle(hMixerDevice);
                        hMixerDevice = NULL;
                }
    }
}

// Watch for PNP events to free up the open handle when needed
// We will assume any changes will now generate a WINMM_DEVICECHANGED message from WinMM
// except for the QUERYREMOVEFAILED case, in this case we will just re-aquire the preferred mixer
//
void Volume_DeviceChange(HWND hWnd,WPARAM wParam,LPARAM lParam)
{
    PDEV_BROADCAST_HANDLE bh = (PDEV_BROADCAST_HANDLE)lParam;
        
    //If we have an handle on the device then we get a DEV_BROADCAST_HDR structure as the lParam.

    if(!DeviceEventContext || !bh || (bh->dbch_devicetype != DBT_DEVTYP_HANDLE))
    {
        return;
    }
        
    switch (wParam)
    {
        case DBT_DEVICEQUERYREMOVE:             // Someone wants to remove this device, let's let them.
        {
                if (g_hMixer)
                    {
                            mixerClose(g_hMixer);
                            g_hMixer = NULL;
                            g_uMixer = 0;
                            g_dwMixerDevNode = 0L;
                    }
                }
            break;

            case DBT_DEVICEQUERYREMOVEFAILED:       // The query failed, the device will not be removed, so lets reopen it.
        {
            Volume_WinMMDeviceChange(hWnd);     // Lets just use this function to do it.
        }
            break;
    }
}

void Volume_WmDestroy(
   HWND hDlg
   )
{
    Volume_DeviceChange_Cleanup();
}

void Volume_Shutdown(
    HWND hWnd)
{
    Volume_UpdateStatus(hWnd, FALSE, FALSE);
}

void Volume_Menu(HWND hwnd, UINT uMenuNum, UINT uButton)
{
    POINT   pt;
    UINT    iCmd;
    HMENU   hmenu;

    GetCursorPos(&pt);

    hmenu = Volume_CreateMenu();
    if (!hmenu)
                return;

    SetForegroundWindow(hwnd);
    iCmd = TrackPopupMenu(hmenu, uButton | TPM_RETURNCMD | TPM_NONOTIFY,
        pt.x, pt.y, 0, hwnd, NULL);

    DestroyMenu(hmenu);
    switch (iCmd) {
        case VOLUMEMENU_PROPERTIES:
            Volume_ControlPanel(hwnd);
            break;

        case VOLUMEMENU_SNDVOL:
            Volume_VolumeControl();
            break;
    }

    SetIconFocus(hwnd, STWM_NOTIFYVOLUME);

}

void Volume_Notify(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
    switch (lParam)
    {
        case WM_RBUTTONUP:
            Volume_Menu(hwnd, 1, TPM_RIGHTBUTTON);
            break;

        case WM_LBUTTONDOWN:
            SetTimer(hwnd, VOLUME_TIMER_ID, GetDoubleClickTime()+100, NULL);
            break;

        case WM_LBUTTONDBLCLK:
            KillTimer(hwnd, VOLUME_TIMER_ID);
            Volume_VolumeControl();
            break;
    }
}


/* WARNING - WARNING - DANGER - DANGER - WARNING - WARNING - DANGER - DANGER */
/* WARNING - WARNING - DANGER - DANGER - WARNING - WARNING - DANGER - DANGER */
/* WARNING - WARNING - DANGER - DANGER - WARNING - WARNING - DANGER - DANGER */
/*
 * MYWM_WAKEUP and the "Tray Volume" window are defined by the SNDVOL32.EXE
 * application.  Changing these values or changing the values in SNDVOL32.EXE
 * without mirroring them here will break the tray volume dialog.
 */
/* WARNING - WARNING - DANGER - DANGER - WARNING - WARNING - DANGER - DANGER */
/* WARNING - WARNING - DANGER - DANGER - WARNING - WARNING - DANGER - DANGER */
/* WARNING - WARNING - DANGER - DANGER - WARNING - WARNING - DANGER - DANGER */

#define MYWM_WAKEUP             (WM_APP+100+6)

void Volume_Timer(HWND hwnd)
{
        KillTimer(hwnd, VOLUME_TIMER_ID);

        Volume_WakeUpOrClose (FALSE);
}

void Volume_WakeUpOrClose(BOOL fClose)
{
        const TCHAR szVolWindow [] = TEXT ("Tray Volume");
        HWND hApp;

        if (hApp = FindWindow(szVolWindow, NULL))
        {
                SendMessage(hApp, MYWM_WAKEUP, (WPARAM)fClose, 0);
        }
        else if (!fClose)
        {
                const TCHAR szOpen[]    = TEXT ("open");
                const TCHAR szVolApp[]  = TEXT ("SNDVOL32.EXE");
                const TCHAR szParamsWakeup[]  = TEXT ("/t");

                ShellExecute (NULL, szOpen, szVolApp, szParamsWakeup, NULL, SW_SHOWNORMAL);
        }
}


/*
 * Volume_ControlPanel
 *
 * Launch "Audio" control panel/property sheet upon request.
 *
 * */
void Volume_ControlPanel(HWND hwnd)
{
        const TCHAR szOpen[]    = TEXT ("open");
        const TCHAR szRunDLL[]  = TEXT ("RUNDLL32.EXE");
        const TCHAR szParams[]  = TEXT ("MMSYS.CPL,ShowFullControlPanel");

        ShellExecute(NULL, szOpen, szRunDLL, szParams, NULL, SW_SHOWNORMAL);
}

/*
 * Volume_VolumeControl
 *
 * Launch Volume Control App
 *
 * */
void Volume_VolumeControl()
{
        const TCHAR szOpen[]    = TEXT ("open");
        const TCHAR szVolApp[]  = TEXT ("SNDVOL32.EXE");

        ShellExecute(NULL, szOpen, szVolApp, NULL, NULL, SW_SHOWNORMAL);
}



/*
 * FileExists
 *
 * Does a file exist
 *
 * */

BOOL FileExists(LPCTSTR pszPath)
{
        return (GetFileAttributes(pszPath) != (DWORD)-1);
} // End FileExists


/*
 * FindSystemFile
 *
 * Finds full path to specified file
 *
 * */

BOOL FindSystemFile(LPCTSTR pszFileName, LPTSTR pszFullPath, UINT cchSize)
{
        TCHAR       szPath[MAX_PATH];
        LPTSTR      pszName;
        DWORD       cchLen;

        if ((pszFileName == NULL) || (pszFileName[0] == 0))
                return FALSE;

        cchLen = SearchPath(NULL, pszFileName, NULL, MAX_PATH,
                                                szPath,&pszName);
        if (cchLen == 0)
                return FALSE;
        
        if (cchLen >= MAX_PATH)
                cchLen = MAX_PATH - 1;

        if (! FileExists (szPath))
                return FALSE;

        if ((pszFullPath == NULL) || (cchSize == 0))
                return TRUE;

           // Copy full path into buffer
        if (cchLen >= cchSize)
                cchLen = cchSize - 1;
        
        lstrcpyn (pszFullPath, szPath, cchLen);
        
        pszFullPath[cchLen] = 0;

        return TRUE;
} // End FindSystemFile
