
/*----------------------------------------------------------------------------*\
 *
 *  MCIWnd
 *
 *----------------------------------------------------------------------------*/

//  This stuff is not going to work 64-bit
#pragma warning(disable:4312)


#include "mciwndi.h"
#ifdef DAYTONA
// Include if debug string is read from registry
#include <profile.h>
#endif

#ifndef _WIN32
// variable args won't work in a DLL with DS!=SS unless _WINDLL is defined
#define WINDLL
#define _WINDLL
#define __WINDLL
#endif

#include <stdarg.h>
#include <stdlib.h>

#if defined CHICAGO && defined _WIN32
#define CHICAGO32
#endif

#define SQUAWKNUMZ(num) #num
#define SQUAWKNUM(num) SQUAWKNUMZ(num)
#define SQUAWK __FILE__ "(" SQUAWKNUM(__LINE__) ") :squawk: "

#ifdef _WIN32
#define MCIWndOpenA(hwnd, sz, f)     (LONG)MCIWndSM(hwnd, MCIWNDM_OPENA, (WPARAM)(UINT)(f),(LPARAM)(LPVOID)(sz))
#endif

#if !defined NUMELMS
    #define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
#endif

#ifdef UNICODE
#include <wchar.h>

//
//  Assist with unicode conversions
//

int Iwcstombs(LPSTR lpstr, LPCWSTR lpwstr, int len)
{
    return WideCharToMultiByte(GetACP(), 0, lpwstr, -1, lpstr, len, NULL, NULL);
}

int Imbstowcs(LPWSTR lpwstr, LPCSTR lpstr, int len)
{
    return MultiByteToWideChar(GetACP(),
                               MB_PRECOMPOSED,
                               lpstr,
                               -1,
                               lpwstr,
                               len);
}

#endif

#ifdef CHICAGO
extern BOOL gfIsRTL;
#else
#define gfIsRTL 0
#endif

LRESULT CALLBACK _LOADDS MCIWndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK _LOADDS SubClassedTrackbarWndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);

STATICFN HBRUSH NEAR PASCAL CreateDitherBrush(void);

STATICFN LRESULT OwnerDraw(PMCIWND p, UINT msg, WPARAM wParam, LPARAM lParam);
STATICFN BOOL NEAR PASCAL mciDialog(HWND hwnd);
STATICFN void NEAR PASCAL MCIWndCopy(PMCIWND p);

BOOL FAR _cdecl _loadds MCIWndRegisterClass(void)
{
    WNDCLASS cls;

    // !!! We need to register a global class with the hinstance of the DLL
    // !!! because it's the DLL that has the code for the window class.
    // !!! Otherwise, the class goes away on us and things start to blow!
    // !!! HACK HACK HACK The hInstance is the current DS which is the high
    // !!! word of the address of all global variables --- sorry NT
#ifndef _WIN32
    HINSTANCE hInstance = (HINSTANCE)HIWORD((LPVOID)&hInst); // random global
#else
    extern HINSTANCE ghInst;	// in video\init.c
    HINSTANCE hInstance = ghInst;
#endif

    // If we're already registered, we're OK
    if (GetClassInfo(hInstance, aszMCIWndClassName, &cls))
	return TRUE;

    // !!! Save the instance that created the class in a global for cutils.c
    // !!! which may need to know this.  I know, it's ugly.
    hInst = hInstance;

    cls.lpszClassName   = aszMCIWndClassName;
    cls.lpfnWndProc     = (WNDPROC)MCIWndProc;
    cls.style           = CS_GLOBALCLASS | CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS | CS_OWNDC;
    cls.hCursor         = LoadCursor(NULL,IDC_ARROW);
    cls.hIcon           = NULL;
    cls.lpszMenuName    = NULL;
    cls.hbrBackground   = (HBRUSH)(COLOR_WINDOW + 1);
    cls.hInstance	= hInstance;
    cls.cbClsExtra      = 0;
    cls.cbWndExtra      = sizeof(LPVOID); // big enough for far pointer

    if (RegisterClass(&cls)) {

        InitCommonControls();

        // !!! Other one-time initialization

	return TRUE;
    }

    return FALSE;
}


//
// Create the window
//
//
#ifdef UNICODE
// HACK ALERT:
// (see comments in MCIWndiCreate
// we set a flag to say that the szFile parameter is unicode.  Otherwise if
// the parent window is ansi (which is likely) we will try and interpret the
// string as ascii.  This is not safe if MCIWndCreate is called on multiple
// threads, but I do not want (currently... let me change my mind) to add
// a critical section to prevent multiple thread access.  Unfortunately
// there is no simple way to pass another parameter to MCIWndiCreate as
// the interface goes through the wndproc WM_CREATE message.

BOOL fFileNameIsUnicode = FALSE;
#endif

HWND FAR _cdecl _loadds MCIWndCreate(HWND hwndParent, HINSTANCE hInstance,
                      DWORD dwStyle, LPCTSTR szFile)
{
    HWND hwnd;
    int x,y,dx,dy;
    DWORD dwStyleEx;

#ifdef _WIN32
    #define GetCurrentInstance()    GetModuleHandle(NULL);
#else
    #define GetCurrentInstance()    SELECTOROF(((LPVOID)&hwndParent))
#endif

    if (hInstance == NULL)
        hInstance = GetCurrentInstance();

    if (!MCIWndRegisterClass())
	return NULL;

    if (HIWORD(dwStyle) == 0)
    {
	if (hwndParent)
	    dwStyle |= WS_CHILD | WS_BORDER | WS_VISIBLE;
	else
	    dwStyle |= WS_OVERLAPPEDWINDOW | WS_VISIBLE;
    }

    // !!! Do we really want to do this?
    dwStyle |= WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

    x = y = dy = 0;
    dx = STANDARD_WIDTH;

    // If we're making a top level window, pick some reasonable position
    if (hwndParent == NULL && !(dwStyle & WS_POPUP)) {
        x = CW_USEDEFAULT;
	// Visible overlapped windows treat y as a ShowWindow flag
	if (dwStyle & WS_VISIBLE)
	    y = SW_SHOW;
    }

    // Our preview open dialog rips if we don't provide a non-zero ID for a
    // child window.
    dwStyleEx = gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0;
#ifdef UNICODE
    fFileNameIsUnicode = TRUE;
    // strictly... we should enter a critical section here to lock out
    // all other MCIWndCreate calls.
#endif
    hwnd = CreateWindowEx(dwStyleEx,
                          aszMCIWndClassName, szNULL, dwStyle,
                          x, y, dx, dy,
                          hwndParent,
                          (HMENU)((dwStyle & WS_CHILD) ? 0x42 : 0),
                          hInstance, (LPVOID)szFile);
#ifdef UNICODE
    fFileNameIsUnicode = FALSE;
#endif
    return hwnd;
}

#ifdef UNICODE
// ansi thunk for above function - stub
HWND FAR _cdecl _loadds MCIWndCreateA(HWND hwndParent, HINSTANCE hInstance,
                    DWORD dwStyle, LPCSTR szFile)
{
    WCHAR * lpW;
    int sz;
    HWND    hwnd;

    if (szFile == NULL) {
        return MCIWndCreateW(hwndParent, hInstance, dwStyle, NULL);
    }

    sz = lstrlenA(szFile) + 1;

    lpW = (WCHAR *) LocalAlloc(LPTR, sizeof(WCHAR) * sz);
    if (!lpW) {
        return NULL;
    }

    Imbstowcs(lpW, szFile, sz);

    hwnd = MCIWndCreateW(hwndParent, hInstance, dwStyle, lpW);

    LocalFree ((HLOCAL)lpW);

    return hwnd;
}
#else
#if defined _WIN32
HWND FAR _loadds MCIWndCreateW(HWND hwndParent, HINSTANCE hInstance,
                    DWORD dwStyle, LPCWSTR szFile)
{
    #pragma message (SQUAWK "maybe later add support here")
    return NULL;
}
#endif
#endif // UNICODE

//
// Give a notification of something interesting to the proper authorites.
//
STATICFN LRESULT NotifyOwner(PMCIWND p, unsigned msg, WPARAM wParam, LPARAM lParam
)
{
    if (p->hwndOwner)
        return SendMessage(p->hwndOwner, msg, wParam, lParam);
    else
        return 0;
}


//
// If an error occured, set our error code and maybe bring up a dialog
// Clears the error code if command was successful.
//
STATICFN void MCIWndiHandleError(PMCIWND p, MCIERROR dw)
{
    TCHAR       ach[128];

    // Set/Clear our error code
    p->dwError = dw;

    if (dw) {

	// We want to bring up a dialog on errors, so do so.
	// Don't bring up a dialog while we're moving the thumb around because
	// that'll REALLY confuse the mouse capture
	if (!(p->dwStyle & MCIWNDF_NOERRORDLG) && !p->fScrolling &&
							!p->fTracking) {
            mciGetErrorString(p->dwError, ach, NUMELMS(ach));
	    MessageBox(p->hwnd, ach, LoadSz(IDS_MCIERROR),
		       MB_ICONEXCLAMATION | MB_OK);
	}

	// The "owner" wants to know the error.  We tell him after we
	// bring up the dialog, because otherwise, our VBX never gets this
	// event.  (Weird...)
	if (p->dwStyle & MCIWNDF_NOTIFYERROR) {
	    NotifyOwner(p, MCIWNDM_NOTIFYERROR, (WPARAM)p->hwnd, p->dwError);
	}

    }
}

//
// Send an MCI GetDevCaps command and return whether or not it's supported
// This will not set our error code
//
STATICFN BOOL MCIWndDevCaps(PMCIWND p, DWORD item)
{
    MCI_GETDEVCAPS_PARMS   mciDevCaps;
    DWORD               dw;

    if (p->wDeviceID == 0)
        return FALSE;

    mciDevCaps.dwItem = (DWORD)item;

    dw = mciSendCommand(p->wDeviceID, MCI_GETDEVCAPS, MCI_GETDEVCAPS_ITEM,
			(DWORD_PTR)(LPVOID)&mciDevCaps);

    if (dw == 0)
	return (BOOL)mciDevCaps.dwReturn;
    else
	return FALSE;
}

//
// Send an MCI Status command.
// This will not set our error code
//
STATICFN DWORD MCIWndStatus(PMCIWND p, DWORD item, DWORD err)
{
    MCI_STATUS_PARMS    mciStatus;
    DWORD               dw;

    if (p->wDeviceID == 0)
	return err;

    mciStatus.dwItem = (DWORD)item;

    dw = mciSendCommand(p->wDeviceID, MCI_STATUS, MCI_STATUS_ITEM,
			(DWORD_PTR)(LPVOID)&mciStatus);

    if (dw == 0)
	return (DWORD) mciStatus.dwReturn;
    else
	return err;
}

//
// Send an MCI String command
// Optionally set our error code.  Never clears it.
//
STATICFN DWORD MCIWndString(PMCIWND p, BOOL fSetErr, LPTSTR sz, ...)
{
    TCHAR   ach[MAX_PATH];
    int     i;
    DWORD   dw;
    va_list va;

    if (p->wDeviceID == 0)
	return 0;

    for (i=0; *sz && *sz != TEXT(' '); )
	ach[i++] = *sz++;

    i += wsprintf(&ach[i], TEXT(" %d "), (UINT)p->alias);

    va_start(va,sz);
    i += wvsprintf(&ach[i], sz, va);
    va_end(va);

    dw = mciSendString(ach, NULL, 0, NULL);

    DPF("MCIWndString('%ls'): %ld",(LPTSTR)ach, dw);

    if (fSetErr)
	MCIWndiHandleError(p, dw);

    return dw;
}


STATICFN long mciwnd_atol(LPTSTR sz)
{
    long l;

    //!!! check for (-) sign?
    for (l=0; *sz >= TEXT('0') && *sz <= TEXT('9'); sz++)
        l = l*10 + (*sz - TEXT('0'));

    return l;
}

#define SLASH(c)     ((c) == TEXT('/') || (c) == TEXT('\\'))

/*--------------------------------------------------------------+
| FileName  - return a pointer to the filename part of szPath   |
|             with no preceding path.                           |
+--------------------------------------------------------------*/
LPTSTR FAR FileName(LPTSTR szPath)
{
    LPCTSTR   sz;

    sz = &szPath[lstrlen(szPath)];
    for (; sz>szPath && !SLASH(*sz) && *sz!=TEXT(':');)
        sz = CharPrev(szPath, sz);
    return (sz>szPath ? (LPTSTR)++sz : (LPTSTR)sz);
}

//
// Sends an MCI String command and converts the return string to an integer
// Optionally sets our error code.  Never clears it.
//
STATICFN DWORD MCIWndGetValue(PMCIWND p, BOOL fSetErr, LPTSTR sz, DWORD err, ...)
{
    TCHAR   achRet[20];
    TCHAR   ach[MAX_PATH];
    DWORD   dw;
    int     i;
    va_list va;

    for (i=0; *sz && *sz != TEXT(' '); )
	ach[i++] = *sz++;

    if (p->wDeviceID)
        i += wsprintf(&ach[i], TEXT(" %d "), (UINT)p->alias);
    va_start(va, err);
    i += wvsprintf(&ach[i], sz, va);  //!!!use varargs
    va_end(va);

    dw = mciSendString(ach, achRet, NUMELMS(achRet), NULL);

    DPF("MCIWndGetValue('%ls'): %ld",(LPTSTR)ach, dw);

    if (fSetErr)
        MCIWndiHandleError(p, dw);

    if (dw == 0) {
        DPF("GetValue('%ls'): %ld",(LPTSTR)ach, mciwnd_atol(achRet));
        return mciwnd_atol(achRet);
    } else {
        DPF("MCIGetValue('%ls'): error=%ld",(LPTSTR)ach, dw);
	return err;
    }
}

//
// Send an MCI command and get the return string back
// This never sets our error code.
//
// Note: szRet can be the same string as sz
//
STATICFN DWORD MCIWndGet(PMCIWND p, LPTSTR sz, LPTSTR szRet, int len, ...)
{
    TCHAR   ach[MAX_PATH];
    int     i;
    DWORD   dw;
    va_list va;

    if (!p->wDeviceID) {
	szRet[0] = 0;
	return 0L;
    }

    for (i=0; *sz && *sz != TEXT(' '); )
	ach[i++] = *sz++;

    i += wsprintf(&ach[i], TEXT(" %d "), (UINT)p->alias);
    va_start(va, len);
    i += wvsprintf(&ach[i], sz, va);  //!!!use varargs
    va_end(va);

    // initialize to NULL return string
    szRet[0] = 0;

    dw = mciSendString(ach, szRet, len, p->hwnd);

    DPF("MCIWndGet('%ls'): '%ls'",(LPTSTR)ach, (LPTSTR)szRet);

    return dw;
}

//
// Gets the source or destination rect from the MCI device
// Does NOT set our error code since this is an internal function
//
STATICFN void MCIWndRect(PMCIWND p, LPRECT prc, BOOL fSource)
{
    MCI_DGV_RECT_PARMS      mciRect;
    DWORD dw=0;

    SetRectEmpty(prc);

    if (p->wDeviceID)
        dw = mciSendCommand(p->wDeviceID, MCI_WHERE,
            (DWORD)fSource ? MCI_DGV_WHERE_SOURCE : MCI_DGV_WHERE_DESTINATION,
            (DWORD_PTR)(LPVOID)&mciRect);

    if (dw == 0)
        *prc = mciRect.rc;

    prc->right  += prc->left;
    prc->bottom += prc->top;
}


STATICFN VOID MCIWndiSizePlaybar(PMCIWND p)
{
    RECT rc;
    UINT w, h;

    // No playbar!!
    if (p->dwStyle & MCIWNDF_NOPLAYBAR)
	return;

    #define SLOP 0     // Left outdent of toolbar

    // How big a window are we putting a toolbar on?
    GetClientRect(p->hwnd, &rc);
    w = rc.right;
    h = rc.bottom;

    SetWindowPos(p->hwndToolbar, NULL,
		-SLOP, h - TB_HEIGHT, w + SLOP, TB_HEIGHT,
		SWP_NOZORDER);

    // Make sure it's visible now
    ShowWindow(p->hwndToolbar, SW_SHOW);

    // Figure out where the toolbar ends and the trackbar begins
    SendMessage(p->hwndToolbar, TB_GETITEMRECT,
	(int)SendMessage(p->hwndToolbar, TB_COMMANDTOINDEX,
		TOOLBAR_END, 0),
	(LPARAM)(LPVOID)&rc);

    // Place the trackbar next to the end of the toolbar
    SetWindowPos(p->hwndTrackbar, HWND_TOP, rc.right,
		h - TB_HEIGHT + 2,
		w - rc.right, TB_HEIGHT,	// !!!
		0);

    //!!! Maybe put menu button on right side of trackbar?  So
    //!!! make sep the right size (size of the track bar!)
}

// Resize the window by the given percentage
// 0 means use DESTINATION rect and size it automatically
STATICFN VOID MCIWndiSize(PMCIWND p, int iSize)
{
    RECT rc, rcT;
    int  dx, dy;

    // If we're given a percentage, we take it from the SOURCE size.
    // For default, (zero), we use the destination size
    if (iSize)
        rc = p->rcNormal; /* get the original "normal size" rect */
    else {
	if (p->wDeviceID)
            MCIWndRect(p, &rc, FALSE);/* get the current (destination) size */
	else
	    SetRect(&rc, 0, 0, 0, 0);
	iSize = 100;
    }

    rc.bottom = MulDiv(rc.bottom, iSize, 100);
    rc.right = MulDiv(rc.right, iSize, 100);

    // Now set the movie to play in the new rect
    if (!IsRectEmpty(&rc))
        MCIWndString(p, FALSE, szPutDest,
	    0, 0, rc.right - rc.left, rc.bottom - rc.top);
	
    // If we're not supposed to resize our window to this new rect, at least
    // we'll fix up the toolbar before we leave (the buttons may have changed)
    if (p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW) {
	MCIWndiSizePlaybar(p);
	return;
    }

    // We're not a windowed device, or we're closed - don't touch our width
    if (IsRectEmpty(&rc)) {
        GetClientRect(p->hwnd, &rcT);
        rc.right = rcT.right;
    }

    // If we will have a playbar, grow the window by its height
    if (!(p->dwStyle & MCIWNDF_NOPLAYBAR))
        rc.bottom += TB_HEIGHT;

    // Now get the size for our window by growing it by its non-client size
    AdjustWindowRect(&rc, GetWindowLong(p->hwnd, GWL_STYLE), FALSE);

    // Now we have the new size for our MCIWND.  If the SetWindowPos didn't
    // actually result in changing its size, it will not generate a WM_SIZE
    // and it won't fix the toolbar or set the dest rect correctly, so we'll
    // have to call a WM_SIZE ourselves.  It's not enough to check the size
    // we are trying to make it, because it may not give us the size we want
    // (WM_GETMINMAXINFO) so we have to compare the original size and the
    // ultimate size.
    // Sometimes if it only changes by one pixel, it STILL won't generate a
    // WM_SIZE.
    GetWindowRect(p->hwnd, &rcT);

    SetWindowPos(p->hwnd, NULL, 0, 0, rc.right - rc.left,
                    rc.bottom - rc.top,
                    SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOMOVE);

    GetWindowRect(p->hwnd, &rc);
    dx = ABS((rcT.right - rcT.left) - (rc.right - rc.left));
    dy = ABS((rcT.bottom - rcT.top) - (rc.bottom - rc.top));
    if (dx < 2 && dy < 2) {
	PostMessage(p->hwnd, WM_SIZE, 0, 0);
    }
}


//
// Figure out the position in ms of the beginning of the track we're on
//
STATICFN DWORD MCIWndiPrevTrack(PMCIWND p)
{
    DWORD	dw;
    int		iTrack;

    if (!p->fHasTracks)
	return 0;

    MCIWndString(p, FALSE, szSetFormatTMSF);
    dw = MCIWndStatus(p, MCI_STATUS_POSITION, 0); // return value is 0xFFSSMMTT
    iTrack = LOWORD(dw) & 0xFF;
    // If we're less than 1 second into the track, choose the previous track
    if ((iTrack > p->iFirstTrack) && (!(LOWORD(dw) & 0xFF00)) &&
			((HIWORD(dw) & 0xFF) == 0))
	iTrack--;
    dw = p->pTrackStart[iTrack - p->iFirstTrack];
    MCIWndString(p, FALSE, szSetFormatMS);
    return dw;
}

//
// Figure out the position in ms of the beginning of the next track
//
STATICFN DWORD MCIWndiNextTrack(PMCIWND p)
{
    DWORD	dw;
    int		iTrack;

    if (!p->fHasTracks)
	return 0;

    MCIWndString(p, FALSE, szSetFormatTMSF);
    dw = MCIWndStatus(p, MCI_STATUS_POSITION, 0); // return value is 0xTTMMSSFF
    iTrack = (LOWORD(dw) & 0xFF) + 1;
    if (iTrack >= p->iNumTracks + p->iFirstTrack)
	iTrack--;
    dw = p->pTrackStart[iTrack - p->iFirstTrack];
    MCIWndString(p, FALSE, szSetFormatMS);
    return dw;
}


//
// Figure out where the tracks begin for making tics
//
STATICFN void MCIWndiCalcTracks(PMCIWND p)
{
    int		i;

    if (!p->fHasTracks)
	return;

    p->iNumTracks = (int)MCIWndGetValue(p, FALSE, szStatusNumTracks, 0);
    p->iFirstTrack = MCIWndGetValue(p, FALSE, szStatusPosTrack, 0, 0) == 0
		? 1 : 0;

    if (p->pTrackStart)
	LocalFree((HANDLE)p->pTrackStart);

    if (p->iNumTracks) {
	p->pTrackStart = (LONG *)LocalAlloc(LPTR,
						p->iNumTracks * sizeof(LONG));
	if (p->pTrackStart == NULL) {
	    p->iNumTracks = 0;
	    p->fHasTracks = FALSE;
	}
	for (i = 0; i < p->iNumTracks; i++) {
	    p->pTrackStart[i] =
		MCIWndGetValue(p, FALSE, szStatusPosTrack, 0,
		    p->iFirstTrack + i);
	}
    }
}


//
// Mark tics on the trackbar for the beginning of tracks
//
STATICFN void MCIWndiMarkTics(PMCIWND p)
{
    int		i;

    if (!p->fHasTracks)
	return;

    SendMessage(p->hwndTrackbar, TBM_SETTIC, 0, p->dwMediaStart);
    for (i = 0; i < p->iNumTracks; i++) {
	SendMessage(p->hwndTrackbar, TBM_SETTIC, 0, p->pTrackStart[i]);
    }
    SendMessage(p->hwndTrackbar, TBM_SETTIC,0, p->dwMediaStart + p->dwMediaLen);
}

STATICFN VOID MCIWndiValidateMedia(PMCIWND p)
{
    DWORD dw;

    if (!p->wDeviceID) {
	p->fMediaValid = FALSE;
	return;
    }

    dw = p->dwMediaLen;
    p->fMediaValid = TRUE;
    p->dwMediaStart = MCIWndGetStart(p->hwnd);
    p->dwMediaLen = MCIWndGetLength(p->hwnd);
    // !!! do something special if len=0?

    // We have a playbar, so set the ranges of the trackbar if we've changed
    if (dw != p->dwMediaLen && !(p->dwStyle & MCIWNDF_NOPLAYBAR)) {
	// must set position first or zero length range won't move thumb
        SendMessage(p->hwndTrackbar, TBM_CLEARTICS, TRUE, 0);
        SendMessage(p->hwndTrackbar, TBM_SETPOS, TRUE, p->dwMediaStart);
	SendMessage(p->hwndTrackbar, TBM_SETRANGEMIN, 0, p->dwMediaStart);
	SendMessage(p->hwndTrackbar, TBM_SETRANGEMAX, 0,
		p->dwMediaStart + p->dwMediaLen);

        MCIWndiCalcTracks(p);
        MCIWndiMarkTics(p);
    }
}

//
// Create the filter for the open dialog.  Caution: Don't overflow pchD !!!
//
STATICFN void MCIWndiBuildMeAFilter(LPTSTR pchD)
{
    LPTSTR      pchS;
    TCHAR       ach[128];

    // Our filter will look like:  "MCI Files\0*.avi;*.wav\0All Files\0*.*\0"
    // The actual extensions for the MCI files will come from the list in
    // the "mci extensions" section of win.ini

    lstrcpy(pchD, LoadSz(IDS_MCIFILES));

    // Creates a list like: "avi\0wav\0mid\0"
    GetProfileString(szMCIExtensions, NULL, szNULL, ach, NUMELMS(ach));
	
    for (pchD += lstrlen(pchD)+1, pchS = ach; *pchS;
		pchD += lstrlen(pchS)+3, pchS += lstrlen(pchS)+1) {
        lstrcpy(pchD, TEXT("*."));
	lstrcpy(pchD + 2, pchS);
        lstrcpy(pchD + 2 + lstrlen(pchS), TEXT(";"));
    }
    if (pchS != ach)
	--pchD;		// erase the last ;
    *pchD = TEXT('\0');
    lstrcpy(++pchD, LoadSz(IDS_ALLFILES));
    pchD += lstrlen(pchD) + 1;
    lstrcpy(pchD, TEXT("*.*\0"));
}

//
// Create the playbar windows we'll need later
//
STATICFN void MCIWndiMakeMeAPlaybar(PMCIWND p)
{
    TBBUTTON            tb[8];
    extern HINSTANCE ghInst;	// in video\init.c
    DWORD               dwStyleEx;


    // They don't want a playbar
    if (p->dwStyle & MCIWNDF_NOPLAYBAR)
	return;


#define MENUSEP 2
    tb[0].iBitmap = MENUSEP;
    tb[0].idCommand = -1;
    tb[0].fsState = 0;
    tb[0].fsStyle = TBSTYLE_SEP;
    tb[0].iString = -1;

    tb[1].iBitmap = 0;
    tb[1].idCommand = MCI_PLAY;
    tb[1].fsState = TBSTATE_ENABLED | TBSTATE_HIDDEN;
    tb[1].fsStyle = TBSTYLE_BUTTON;
    tb[1].iString = -1;

    tb[2].iBitmap = 2;
    tb[2].idCommand = MCI_STOP;
    tb[2].fsState = TBSTATE_ENABLED | TBSTATE_HIDDEN;
    tb[2].fsStyle = TBSTYLE_BUTTON;
    tb[2].iString = -1;

    tb[3].iBitmap = 4;
    tb[3].idCommand = MCI_RECORD;
    tb[3].fsState = TBSTATE_ENABLED | TBSTATE_HIDDEN;
    tb[3].fsStyle = TBSTYLE_BUTTON;
    tb[3].iString = -1;

    tb[4].iBitmap = 5;
    tb[4].idCommand = IDM_MCIEJECT;
    tb[4].fsState = TBSTATE_ENABLED | TBSTATE_HIDDEN;
    tb[4].fsStyle = TBSTYLE_BUTTON;
    tb[4].iString = -1;

    tb[5].iBitmap = MENUSEP;
    tb[5].idCommand = -1;
    tb[5].fsState = 0;
    tb[5].fsStyle = TBSTYLE_SEP;
    tb[5].iString = -1;

    tb[6].iBitmap = 3;
    tb[6].idCommand = IDM_MENU;
    tb[6].fsState = TBSTATE_ENABLED;
    tb[6].fsStyle = TBSTYLE_BUTTON;
    tb[6].iString = -1;

    tb[7].iBitmap = 4;
    tb[7].idCommand = TOOLBAR_END;
    tb[7].fsState = 0;
    tb[7].fsStyle = TBSTYLE_SEP;
    tb[7].iString = -1;

//    if (p->hbmToolbar)
//	DeleteObject(p->hbmToolbar);
    // Must use DLL's ghInst to get Bitmap
//    p->hbmToolbar = LoadBitmap(ghInst, MAKEINTRESOURCE(IDBMP_TOOLBAR));

    // Create invisible for now so it doesn't flash
    p->hwndToolbar = CreateToolbarEx(p->hwnd, TBSTYLE_BUTTON | TBSTYLE_TOOLTIPS
        | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
            CCS_NOPARENTALIGN | CCS_NORESIZE,
        ID_TOOLBAR, 8,
//	NULL, (UINT)p->hbmToolbar,
	ghInst, IDBMP_TOOLBAR,
	(LPTBBUTTON)&tb[0], 8,
        13, 13, 13, 13, sizeof(TBBUTTON));	// buttons are 13x13

    dwStyleEx = gfIsRTL ? WS_EX_LEFTSCROLLBAR | WS_EX_RIGHT | WS_EX_RTLREADING : 0;
    p->hwndTrackbar = CreateWindowEx(dwStyleEx,
        TRACKBAR_CLASS, NULL,
        WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        0, 0, 0, 0, p->hwnd, NULL, GetWindowInstance(p->hwnd), NULL);

    // The trackbar eats key presses so we need to sub class it to see when
    // CTRL-1, CTRL-2, etc. are pressed
    fnTrackbarWndProc = (WNDPROC)GetWindowLongPtr(p->hwndTrackbar, GWLP_WNDPROC);
    SetWindowLongPtr(p->hwndTrackbar, GWLP_WNDPROC,
		(LONG_PTR)SubClassedTrackbarWndProc);

    // Force ValidateMedia to actually update
    p->dwMediaStart = p->dwMediaLen = 0;

    // Set the proper range for the scrollbar
    MCIWndiValidateMedia(p);
}


//
// Gray/ungray toolbar buttons as necessary
//
STATICFN void MCIWndiPlaybarGraying(PMCIWND p)
{
    DWORD	dwMode;

    if (!(p->dwStyle & MCIWNDF_NOPLAYBAR)) {
	dwMode = MCIWndGetMode(p->hwnd, NULL, 0);

	if (dwMode == MCI_MODE_PLAY) {
	    // Hide PLAY Show STOP
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_PLAY, TRUE);
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_STOP, FALSE);
	    SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		MCI_STOP, TRUE);
	    if (p->fCanRecord)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    MCI_RECORD, FALSE);	// !!! can't record ???
	    if (p->fCanEject)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    IDM_MCIEJECT, TRUE);

	// Treat PAUSE mode like STOP mode
	} else if (dwMode == MCI_MODE_PAUSE ||
		   dwMode == MCI_MODE_STOP) {
	    // Hide STOP Show PLAY
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_STOP, TRUE);
	    if (p->fCanPlay) {
		SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		    MCI_PLAY, FALSE);
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    MCI_PLAY, TRUE);
	    }
	    if (p->fCanRecord)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    MCI_RECORD, TRUE);
	    if (p->fCanEject)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    IDM_MCIEJECT, TRUE);

	} else if (dwMode == MCI_MODE_RECORD) {
	    // Hide PLAY Show STOP
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_PLAY, TRUE);
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_STOP, FALSE);
	    SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		MCI_STOP, TRUE);
	    SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		MCI_RECORD, FALSE);
	    if (p->fCanEject)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    IDM_MCIEJECT, TRUE);	// !!! safe ???

	    // recording can change the length
	    p->fMediaValid = FALSE;

	} else if (dwMode == MCI_MODE_SEEK) {
	    // Hide PLAY Show STOP
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_PLAY, TRUE);
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_STOP, FALSE);
	    SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		MCI_STOP, TRUE);
	    if (p->fCanRecord)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    MCI_RECORD, FALSE);
	    if (p->fCanEject)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    IDM_MCIEJECT, FALSE);
	} else {
	    // OPEN, NOT_READY, etc. etc.
	    // Disable everything
	    if (p->fCanPlay) {
		SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		    MCI_PLAY, FALSE);
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    MCI_PLAY, FALSE);
	    }
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON,
		MCI_STOP, TRUE);
	    if (p->fCanRecord)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    MCI_RECORD, FALSE);
	    if (p->fCanEject)
		SendMessage(p->hwndToolbar, TB_ENABLEBUTTON,
		    IDM_MCIEJECT, FALSE);

	    // Clear all tics
	    SendMessage(p->hwndTrackbar, TBM_CLEARTICS,1,0);

	    // Clean out the trackbar
	    // Make a note to re-query start, length later
	    SendMessage(p->hwndTrackbar, TBM_SETPOS,
				TRUE, 0); // set b4 range
	    SendMessage(p->hwndTrackbar, TBM_SETRANGE,
				0, 0);
	    p->fMediaValid = FALSE;
	}
    }
}


//
// Set up the toolbar to have the right buttons
//
STATICFN void MCIWndiFixMyPlaybar(PMCIWND p)
{
    if (p->dwStyle & MCIWNDF_NOPLAYBAR)
	return;

    if (!p->wDeviceID) {
	//
        // gray the toolbar, go to some default buttons, and set zero len track
        //
        if (!(p->dwStyle & MCIWNDF_NOPLAYBAR)) {
            SendMessage(p->hwndToolbar, TB_HIDEBUTTON,   MCI_PLAY,    FALSE);
            SendMessage(p->hwndToolbar, TB_ENABLEBUTTON, MCI_PLAY,    FALSE);
            SendMessage(p->hwndToolbar, TB_HIDEBUTTON,   MCI_STOP,    TRUE );
            SendMessage(p->hwndToolbar, TB_HIDEBUTTON,   MCI_RECORD,  TRUE );
            SendMessage(p->hwndToolbar, TB_HIDEBUTTON,   IDM_MCIEJECT,TRUE );
            SendMessage(p->hwndToolbar, TB_HIDEBUTTON,   IDM_MENU,
		p->dwStyle & MCIWNDF_NOMENU);

            SendMessage(p->hwndTrackbar, TBM_SETPOS, TRUE, 0); // set b4 range
            SendMessage(p->hwndTrackbar, TBM_SETRANGE, 0, 0);
	}
    }

    if (p->wDeviceID) {
	//
	// Use the appropriate buttons
	//
        if (p->fCanPlay)
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON, MCI_PLAY, FALSE);
        else
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON, MCI_PLAY, TRUE);
        if (p->fCanRecord && (p->dwStyle & MCIWNDF_RECORD))
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON, MCI_RECORD, FALSE);
        else
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON, MCI_RECORD, TRUE);
        if (p->fCanEject)
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON, IDM_MCIEJECT, FALSE);
        else
	    SendMessage(p->hwndToolbar, TB_HIDEBUTTON, IDM_MCIEJECT, TRUE);

        SendMessage(p->hwndToolbar, TB_HIDEBUTTON, IDM_MENU,
		p->dwStyle & MCIWNDF_NOMENU);

	// COMMCTRL toolbar bug ... re-arranging buttons screws up the state
	// of the existing buttons, so we better re-gray things
	MCIWndiPlaybarGraying(p);
    }
}

//
// Make an appropriate menu
//
STATICFN void MCIWndiMakeMeAMenu(PMCIWND p)
{
    HMENU hmenu, hmenuWindow = NULL, hmenuVolume = NULL, hmenuSpeed = NULL;
    int	  i;
    WORD  j;

    //
    // Create the floating popup menu BY HAND since we have no resource file
    //

    // Destroy the old menu
    if (p->hmenu) {
        DestroyMenu(p->hmenu);
	if (p->hbrDither) {
	    DeleteObject(p->hbrDither);
	    p->hbrDither = NULL;
	}
    }
    p->hmenu = NULL;
    p->hmenuVolume = NULL;
    p->hmenuSpeed = NULL;


    // We don't want a menu!
    if (p->dwStyle & MCIWNDF_NOMENU)
	return;

    //
    // If we don't want an open command, and nothing's open, don't make
    // a menu.
    //
    if (!p->wDeviceID && (p->dwStyle & MCIWNDF_NOOPEN))
	return;

    //
    // Create the WINDOW sub-popup
    // !!! Do we want to have this menu if an AUTOSIZE flag is off?
    //
    if (p->wDeviceID && p->fCanWindow) {
	hmenuWindow = CreatePopupMenu();
	if (hmenuWindow) {
            AppendMenu(hmenuWindow, MF_ENABLED, IDM_MCIZOOM+50,
			LoadSz(IDS_HALFSIZE));
	    AppendMenu(hmenuWindow, MF_ENABLED, IDM_MCIZOOM+100,
			LoadSz(IDS_NORMALSIZE));
	    AppendMenu(hmenuWindow, MF_ENABLED, IDM_MCIZOOM+200,
			LoadSz(IDS_DOUBLESIZE));
	}
    }

    //
    // Create the VOLUME sub-popup
    //
    if (p->wDeviceID && p->fVolume) {
	hmenuVolume = CreatePopupMenu();
        if (hmenuVolume) {

	    // !!! Hack
	    // Put a bad menu item at the top.  When WINDOWS tries to select
	    // it after we bring up the menu, we won't let it.  We want the
	    // thumb to stay on the current value.
            AppendMenu(hmenuVolume, MF_ENABLED | MF_OWNERDRAW,
			IDM_MCIVOLUME + VOLUME_MAX + 1, NULL);

	    // Create all the Real menu items.  Make the menu VOLUME_MAX items
	    // tall even though the number of unique entries may be less
            for (i=IDM_MCIVOLUME + p->wMaxVol; i>=IDM_MCIVOLUME; i-=5)
		for (j=0; j < VOLUME_MAX / p->wMaxVol; j++)
                    AppendMenu(hmenuVolume, MF_ENABLED | MF_OWNERDRAW, i, NULL);

	    // Now put a filler item at the bottom so every REAL item falls
 	    // inside the channel and there's a unique thumb position for each
	    // item.
            AppendMenu(hmenuVolume, MF_ENABLED | MF_OWNERDRAW,
			IDM_MCIVOLUME + VOLUME_MAX + 2, NULL);

	    // Now CHECK the current volume so the thumb can draw there
	    // round to nearest 5 so it matches a menu item identifier
            i = ((int)MCIWndGetValue(p, FALSE, szStatusVolume, 1000) / 50) * 5;
            CheckMenuItem(hmenuVolume, IDM_MCIVOLUME + i, MF_CHECKED);
        }
    }

    //
    // Create the SPEED sub-popup
    //
    if (p->wDeviceID && p->fSpeed) {
	hmenuSpeed = CreatePopupMenu();
	if (hmenuSpeed) {

	    // !!! Hack from Hell
	    // Put a bogus menu item at the top.  When WINDOWS tries to select
	    // it after we bring up the menu, we won't let it.  We want the
	    // thumb to stay on the current value.
            AppendMenu(hmenuSpeed, MF_ENABLED | MF_OWNERDRAW,
			IDM_MCISPEED + SPEED_MAX + 1, NULL);

	    // Create all the Real menu items
            for (i=IDM_MCISPEED + SPEED_MAX; i>=IDM_MCISPEED; i-=5)
                AppendMenu(hmenuSpeed, MF_ENABLED | MF_OWNERDRAW, i, NULL);

	    // Now put a filler item at the bottom so every REAL item falls
 	    // inside the channel and there's a unique thumb position for each
	    // item.
            AppendMenu(hmenuSpeed, MF_ENABLED | MF_OWNERDRAW,
			IDM_MCISPEED + SPEED_MAX + 2, NULL);

	    // Now CHECK the current speed so the thumb can draw there
	    // round to nearest 5 so it matches a menu item identifier
            i = ((int)MCIWndGetValue(p, FALSE, szStatusSpeed, 1000) / 50) * 5;
            CheckMenuItem(hmenuSpeed, IDM_MCISPEED + i, MF_CHECKED);
        }
    }

    hmenu = CreatePopupMenu();

    if (hmenu) {

	if (p->wDeviceID && p->dwStyle & MCIWNDF_NOPLAYBAR) {
	    if (p->fCanPlay) {
	    	AppendMenu(hmenu, MF_ENABLED, MCI_PLAY, LoadSz(IDS_PLAY));
	    	AppendMenu(hmenu, MF_ENABLED, MCI_STOP, LoadSz(IDS_STOP));
	    }
	    if (p->fCanRecord && (p->dwStyle & MCIWNDF_RECORD))
	        AppendMenu(hmenu, MF_ENABLED, MCI_RECORD, LoadSz(IDS_RECORD));
	    if (p->fCanEject)
	    	AppendMenu(hmenu, MF_ENABLED, IDM_MCIEJECT, LoadSz(IDS_EJECT));
	    if (p->fCanPlay ||
			(p->fCanRecord && (p->dwStyle & MCIWNDF_RECORD)) ||
			p->fCanEject)
                AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
	
	}

	if (hmenuWindow)
            AppendMenu(hmenu, MF_ENABLED|MF_POPUP, (UINT_PTR)hmenuWindow,
		LoadSz(IDS_VIEW));
	if (hmenuVolume)
	    AppendMenu(hmenu, MF_ENABLED|MF_POPUP, (UINT_PTR)hmenuVolume,
		LoadSz(IDS_VOLUME));
	if (hmenuSpeed)
            AppendMenu(hmenu, MF_ENABLED|MF_POPUP, (UINT_PTR)hmenuSpeed,
		LoadSz(IDS_SPEED));

	if (hmenuWindow || hmenuVolume || hmenuSpeed)
            AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);

        if (p->wDeviceID && p->fCanRecord && (p->dwStyle & MCIWNDF_RECORD))
            AppendMenu(hmenu, MF_ENABLED, IDM_MCINEW, LoadSz(IDS_NEW));

	if (!(p->dwStyle & MCIWNDF_NOOPEN))
	    AppendMenu(hmenu, MF_ENABLED, IDM_MCIOPEN,  LoadSz(IDS_OPEN));

        if (p->wDeviceID && p->fCanSave && (p->dwStyle & MCIWNDF_RECORD))
            AppendMenu(hmenu, MF_ENABLED, MCI_SAVE, LoadSz(IDS_SAVE));

	if (p->wDeviceID) {
	    if (!(p->dwStyle & MCIWNDF_NOOPEN)) {
		AppendMenu(hmenu, MF_ENABLED, IDM_MCICLOSE, LoadSz(IDS_CLOSE));
	
                AppendMenu(hmenu, MF_SEPARATOR, 0, NULL);
	    }

	    AppendMenu(hmenu, p->fAllowCopy ? MF_ENABLED : MF_GRAYED,
				IDM_COPY, LoadSz(IDS_COPY));
	
	    if (p->fCanConfig)
                AppendMenu(hmenu, MF_ENABLED, IDM_MCICONFIG,
			LoadSz(IDS_CONFIGURE));

	    // !!! Should we only show this in debug, or if a flag is set?
            AppendMenu(hmenu, MF_ENABLED, IDM_MCICOMMAND, LoadSz(IDS_COMMAND));
	}

	p->hmenu = hmenu;
	p->hmenuVolume = hmenuVolume;
	p->hmenuSpeed = hmenuSpeed;

        p->hbrDither = CreateDitherBrush(); // we'll need this for OwnerDraw
    }
}

//
// Set up everything for an empty window
//
STATICFN LONG MCIWndiClose(PMCIWND p, BOOL fRedraw)
{
    MCI_GENERIC_PARMS   mciGeneric;

    // Oh no!  The MCI device (probably MMP) has hooked our window proc and if
    // we close the device, it will go away, and the hook will DIE!  We need to
    // do everything BUT the closing of the device.  We'll delay that.
    if (GetWindowLongPtr(p->hwnd, GWLP_WNDPROC) != (LONG_PTR)MCIWndProc &&
    		p->wDeviceID && p->fCanWindow) {
        MCIWndString(p, FALSE, szWindowHandle, NULL);	// GO AWAY, DEVICE!
	PostMessage(p->hwnd, MCI_CLOSE, 0, p->wDeviceID);
    } else if (p->wDeviceID)
	// buggy drivers crash if we pass a null parms address
        mciSendCommand(p->wDeviceID, MCI_CLOSE, 0, (DWORD_PTR)(LPVOID)&mciGeneric);

    //
    // if the device had a palette, we need to send palette changes to
    // every window because we just deleted the palette that was realized.
    //
    if (p->fHasPalette) {
	// If we're dying this won't go through unless we SEND
	SendMessage(p->hwnd, MCIWNDM_PALETTEKICK, 0, 0);
    }

    // execute this function even if there's no deviceID since we may want
    // to gray things

    // The next timer will kill itself since wDeviceID is NULL
    p->wDeviceID = 0;
    p->achFileName[0] = 0;	// kill the filename
    p->dwMediaLen = 0;		// so next open will invalidate media

    // We don't want to redraw cuz we're opening a new file right away
    if (!fRedraw)
	return 0;

    // One of the show bits is on... clear the caption
    if (p->dwStyle & MCIWNDF_SHOWALL)
        SetWindowText(p->hwnd, LoadSz(IDS_NODEVICE));

    // Gray all the stuff on the playbar
    MCIWndiFixMyPlaybar(p);

    // Make an appropriate menu for our null device
    MCIWndiMakeMeAMenu(p);

    // Possibly snap ourselves to a small size since there's no device loaded
    // Also reposition the toolbar after it's been fixed up
    MCIWndiSize(p, 0);

    // We need to notify our "owner" that we've closed
    // note that a unicode szNull is also a valid ansi szNull.
    // so we dont have to thunk this for MCIWNDF_NOTIFYANSI
    // !!! This flag can have more than one bit set so the test is different
    if (p->dwStyle & MCIWNDF_NOTIFYMEDIA & ~MCIWNDF_NOTIFYANSI)
        NotifyOwner(p, MCIWNDM_NOTIFYMEDIA, (WPARAM)p->hwnd,
		(LPARAM)(LPVOID)szNULL);

    InvalidateRect(p->hwnd, NULL, TRUE);
    return 0;
}

#ifdef UNICODE
//
// Check to see if our parent is unicode.  We need this test to determine
// if the passed filename is unicode or ascii
//
BOOL TestForUnicode(HWND hwnd)
{
    HWND    hwndSave;

    /*
    ** Find the top level window associated with hwnd
    */
    hwndSave = hwnd;

    while ( hwndSave != (HWND)NULL ) {

        hwnd = hwndSave;
        hwndSave = GetParent( hwndSave );
    }
    return(IsWindowUnicode(hwnd));
}
#endif

//
// This is the WM_CREATE msg of our WndProc
//
STATICFN BOOL MCIWndiCreate(HWND hwnd, LPARAM lParam)
{
    PMCIWND             p;
    DWORD_PTR		dw;
    TCHAR               ach[20];
    HWND                hwndP;

    p = (PMCIWND)LocalAlloc(LPTR, sizeof(MCIWND));

    if (!p)
        return FALSE;

    SetWindowLongPtr(hwnd, 0, (UINT_PTR)p);

    p->hwnd = hwnd;
    p->hwndOwner = GetParent(hwnd);	// we'll send notifications here
    // Otherwise see if there's an owner
    if (p->hwndOwner == NULL)
	p->hwndOwner = GetWindowOwner(hwnd);
    p->alias = (UINT)(UINT_PTR)hwnd;
    p->dwStyle = GetWindowLong(hwnd, GWL_STYLE);

    DragAcceptFiles(p->hwnd, (p->dwStyle & (MCIWNDF_NOMENU | MCIWNDF_NOOPEN)) == 0);

    if (!(p->dwStyle & WS_CAPTION))
          p->dwStyle &= ~MCIWNDF_SHOWALL;

    // !!! Don't remove NOTIFY bits if there's no owner, because someone might
    // set one later.

    dw = (DWORD_PTR)((LPCREATESTRUCT)lParam)->lpCreateParams;

    //
    // see if we are in a MDIClient
    //
    if ((p->dwStyle & WS_CHILD) && (hwndP = GetParent(hwnd))) {
        GetClassName(hwndP, ach, NUMELMS(ach));
        p->fMdiWindow = lstrcmpi(ach, szMDIClient) == 0;

        if (p->fMdiWindow)
            dw = ((LPMDICREATESTRUCT)dw)->lParam;
    }

    MCIWndiMakeMeAPlaybar(p);

//  if (szOpenFilter[0] == 0)
//      MCIWndiBuildMeAFilter(szOpenFilter);

    // Set the default timer frequencies
    p->iActiveTimerRate = ACTIVE_TIMER;
    p->iInactiveTimerRate = INACTIVE_TIMER;

    // initialize the OFN structure we'll use to open files
    p->achFileName[0] = TEXT('\0');
    p->ofn.lStructSize = sizeof(OPENFILENAME);
    p->ofn.hwndOwner = hwnd;
    p->ofn.hInstance = NULL;
//  p->ofn.lpstrFilter = szOpenFilter;
    p->ofn.lpstrCustomFilter = NULL;
    p->ofn.nMaxCustFilter = 0;
    p->ofn.nFilterIndex = 0;
;   p->ofn.lpstrFile = p->achFileName;
;   p->ofn.nMaxFile = NUMELMS(p->achFileName);
    p->ofn.lpstrFileTitle = NULL;
    p->ofn.nMaxFileTitle = 0;
    p->ofn.lpstrInitialDir = NULL;
    p->ofn.lpstrTitle = NULL; // "Open Device";
    p->ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
    p->ofn.nFileOffset = 0;
    p->ofn.nFileExtension = 0;
    p->ofn.lpstrDefExt = NULL;
    p->ofn.lCustData = 0;
    p->ofn.lpfnHook = NULL;
    p->ofn.lpTemplateName = NULL;

    p->hicon = LoadIcon(hInst, MAKEINTRESOURCE(MPLAYERICON));

    // Gray stuff; disable things that aren't applicable with no device loaded
    MCIWndClose(hwnd);

#ifndef UNICODE
    if (dw && *(LPSTR)dw)     // treat extra parm as a filename
        MCIWndOpen(hwnd, (LPSTR)dw, 0);

#else //UNICODE
    /*
    ** Check that owner window is also Unicode.
    ** First check that there is a parameter to pass
    */

    if (dw) {

        // We now have to work out whether the extra parameter is
	// a unicode or ascii string pointer.  If we have gone through
	// our internal ascii open routines then the parameter will have
	// been converted to unicode.  If an application has registered the
	// mciwnd class, and then creates a window directly the conversion
	// will not have happened.

	if (fFileNameIsUnicode
	    || TestForUnicode(p->hwndOwner)) {

            if (dw && *(LPWSTR)dw)     // treat extra parm as a filename
                MCIWndOpen(hwnd, (LPWSTR)dw, 0);
        }
        else {

            if (dw && *(LPSTR)dw) {   // treat extra parm as a filename
                MCIWndOpenA(hwnd, (LPSTR)dw, 0);
            }
        }
    }

#endif

    return TRUE;
}

//
// Brings up an OpenDialog or a SaveDialog for the application and returns the
// filename.  Returns TRUE if a file name was chosen, FALSE on error or CANCEL.
//
STATICFN BOOL MCIWndOpenDlg(PMCIWND p, BOOL fSave, LPTSTR szFile, int len)
{
    BOOL f;

    // !!! Maybe this is a device name and our GetOpenFileName will fail.
    // !!! Find someway of bringing up an initial filename anyway?
    szFile[0] = 0;

    p->ofn.lpstrFile = szFile;
    p->ofn.nMaxFile = len;
    if (fSave)
        p->ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
    else
        p->ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;

    //
    // use achReturn to hold the MCI Filter.
    //
    MCIWndiBuildMeAFilter(p->achReturn);
    p->ofn.lpstrFilter = p->achReturn;

    /* prompt user for file to open or save */
    if (fSave)
        f = GetSaveFileNamePreview(&(p->ofn));
    else
        f = GetOpenFileNamePreview(&(p->ofn));

    return f;
}

// Set our timer, if it's needed
STATICFN void MCIWndiSetTimer(PMCIWND p)
{
    // We need a TIMER to notify the "owner" when things change
    if (!(p->dwStyle & MCIWNDF_NOPLAYBAR) ||
         (p->dwStyle & MCIWNDF_NOTIFYMODE) ||
         (p->dwStyle & MCIWNDF_SHOWMODE) ||
         (p->dwStyle & MCIWNDF_SHOWPOS) ||
	 (p->dwStyle & MCIWNDF_NOTIFYPOS)) {
	p->wTimer = SetTimer(p->hwnd, TIMER1,
		p->fActive? p->iActiveTimerRate : p->iInactiveTimerRate,
		NULL);
    }
}

//
// Set the caption based on what they want to see... Name? Pos? Mode?
//
STATICFN VOID MCIWndiSetCaption(PMCIWND p)
{
    TCHAR        ach[200], achMode[40], achT[40], achPos[40];

    // Don't touch their window text if they don't want us to
    if (!(p->dwStyle & MCIWNDF_SHOWALL))
	return;

    ach[0] = TEXT('\0');

    if (p->wDeviceID == 0)
	return;

    if (p->dwStyle & MCIWNDF_SHOWNAME)
        wsprintf(ach, TEXT("%s"), FileName(p->achFileName));

    if (p->dwStyle & (MCIWNDF_SHOWPOS | MCIWNDF_SHOWMODE))
        lstrcat(ach, TEXT(" ("));

    if (p->dwStyle & MCIWNDF_SHOWPOS) {

	// Get the pretty version of the position as a string
        MCIWndGetPositionString(p->hwnd, achPos, NUMELMS(achPos));

        if (p->dwStyle & MCIWNDF_SHOWMODE)
            wsprintf(achT, TEXT("%s - "), (LPTSTR)achPos);
	else
            wsprintf(achT, TEXT("%s"), (LPTSTR)achPos);
	lstrcat(ach, achT);
    }

    if (p->dwStyle & MCIWNDF_SHOWMODE) {
        MCIWndGet(p, szStatusMode, achMode, NUMELMS(achMode));
	lstrcat(ach, achMode);
    }

    if (p->dwStyle & (MCIWNDF_SHOWPOS | MCIWNDF_SHOWMODE))
        lstrcat(ach, TEXT(")"));

    SetWindowText(p->hwnd, ach);
}

//
// Save a file.  Returns 0 for success
//
STATICFN LRESULT MCIWndiSave(PMCIWND p, WPARAM wFlags, LPTSTR szFile)
{
    TCHAR    ach[128];
    LRESULT    dw;

    //
    // If we don't have a filename to save, then get one from a dialog
    //
    if (szFile == (LPVOID)-1L) {
	lstrcpy(ach, p->achFileName);
        if (!MCIWndOpenDlg(p, TRUE, ach, NUMELMS(ach)))
            return -1;
        szFile = ach;
    }

    //
    // HACK HACK: This is a temporary method of stopping a very long file name
    // from overflowing the internal members of the MCIWND struct, which uses
    // only 128 chars for achFileName as well as in achReturn array.
    // Since this was designed in the days of Windows 3.1, 128 chars for handling
    // filename was more than sufficient.  So we'll fail the call for any filename
    // longer than that.  That will keep it from overflowing the internal buffers.
    //
#define MAX_FILENAME_LENGTH   128
    if (lstrlen(szFile) >= MAX_FILENAME_LENGTH)
    {
        DPF("WARNING: MCIWndiSave() got very long (%d chars) filename.\n", lstrlen(szFile)) ;
        p->dwError = MCIERR_INVALID_FILE ;  // error out
        return p->dwError ;
    }

    // !!! All good little boys should be saving to background... don't wait
    dw = MCIWndString(p, TRUE, szSave, szFile);
    if (dw == 0) {
	// Fix the filename of the current file
	lstrcpy(p->achFileName, szFile);
	// Fix the window caption
	MCIWndiSetCaption(p);
	// It's now OK to copy again. (For MCIWAVE, something freshly
	// recorded isn't persisted to the file and wouldn't have been
	// copied with our copy command, so it was disabled).
	p->fAllowCopy = TRUE;
	MCIWndiMakeMeAMenu(p);
    }
    return dw;
}

//
// Actually open a file and set up the window.  Returns 0 for success
//
STATICFN LRESULT MCIWndiOpen(PMCIWND p, WPARAM wFlags, LPTSTR szFile)
{
    DWORD               dw = 0;
    HCURSOR             hcurPrev;
    TCHAR               ach[128];
    UINT                wDeviceID;
    BOOL 		fNew = (wFlags & MCIWNDOPENF_NEW) != 0;

    //
    // We're opening an existing file, szFile is that filename
    // If we don't have a filename to open, then get one from a dialog
    //
    if (!fNew && szFile == (LPVOID)-1L) {
	lstrcpy(ach, p->achFileName);
        if (!MCIWndOpenDlg(p, FALSE, ach, NUMELMS(ach)))
            return -1;
        szFile = ach;
    }

    //
    // We want to open a new file, szFile is the device to open
    // If it's NULL, we use the current device
    //
    if (fNew && (szFile == NULL || *szFile == 0)) {
	// There is no device, so we can't do anything
	if (!p->wDeviceID)
	    return 42;	// !!! failure
        MCIWndGetDevice(p->hwnd, ach, NUMELMS(ach));
	szFile = ach;
    }

    // save the current device ID so we can put it back in case open fails.
    wDeviceID = p->wDeviceID;
    KillTimer(p->hwnd, TIMER1);	// setting the deviceID to 0 will mess up timer
    p->wDeviceID = 0;		// and if open fails, we don't want that
    p->alias++;			// use a new alias

    //
    // HACK HACK: Same too long file name problem (as in MCIWndiSave) is plugged
    // by erroring out on a file name longer than 128 chars.
    //
// #define MAX_FILENAME_LENGTH   128
    if (lstrlen(szFile) >= MAX_FILENAME_LENGTH)
    {
        DPF("WARNING: MCIWndiOpen() got very long (%d chars) filename.\n", lstrlen(szFile)) ;
        p->dwError = MCIERR_INVALID_FILE ;  // error out
        return p->dwError ;
    }

    /*
     * Show the hourglass cursor -- who knows how long this stuff
     * will take
     */
    hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));

    // Open a NEW file
    if (fNew) {
	dw = MCIWndGetValue(p, TRUE, szNew, 0,
		szFile, (UINT)p->alias);

    // open an existing file
    } else {

 	// first, try to open it shareable
        // don't show or update errors since we try to open it twice
        //
        // dont try shareable for "file" devices
        // hack to check for extension.
        //
#ifdef LATER
   !!! HACK ALERT !!!
	For NT (and probably Chicago) this test is VERY error prone.  If
	we have a long file name then we are in big trouble....
#endif
#if 0 //  IF the file exists, then do not open shareable.
    // This section, or something like it, should replace the gross hack
    // below.  Checking for a '.' as the fourth last character... yuk!
	{
	DWORD attr = GetFileAttributes(szFile);
	if (attr != (DWORD)(~0))
	    // The error value is -1, i.e. bitwise inverse of 0
            dw = 0;   // Force non shareable
        else
            dw = MCIWndGetValue(p, FALSE, szOpenShareable, 0,
                (LPTSTR)szFile, (UINT)p->alias);
	}
#else
	{
	    UINT n = lstrlen(szFile);
            if (n > 4 && szFile[n-4] == TEXT('.'))
                dw = 0;
            else
                dw = MCIWndGetValue(p, FALSE, szOpenShareable, 0,
                    (LPTSTR)szFile, (UINT)p->alias);
	}
#endif

        // Error! Try again, not shareable.
        if (dw == 0) {
            dw = MCIWndGetValue(p, FALSE, szOpen, 0,
                (LPTSTR)szFile, (UINT)p->alias);
	    // Last ditch attempt! Try AVI. It'll open anything.  This time,
	    // show, set errors.
	    if (dw == 0) {
                dw = MCIWndGetValue(p, TRUE, szOpenAVI, 0,
                    (LPTSTR)szFile, (UINT)p->alias);
	    }
	}
    }

    if (hcurPrev)
	SetCursor(hcurPrev);

    //
    // Ack! No deviceID... we failed to open
    //
    if (dw == 0)
    {
        p->wDeviceID = wDeviceID;
        MCIWndiSetTimer(p);	// Put the timer back now that DeviceID is back
//	p->achFileName[0] = 0;	// don't hurt the old filename!
	p->alias--;		// back to old alias
	// in case error box or open box wiped us out and we didn't paint
	// because our p->wDeviceID was null because of our open hack
	InvalidateRect(p->hwnd, NULL, TRUE);
        return p->dwError;
    }

    //
    // it worked, now close the old device and open the new.
    //
    if (wDeviceID)
    {
	p->wDeviceID = wDeviceID;
	p->alias--;	// back to old alias so the close might actually work
	MCIWndiClose(p, FALSE);	// don't redraw
	p->alias++;	// new alias again (ACK!)
    }

    p->wDeviceID = (UINT)dw;
    p->dwMode = (DWORD)~0L;	// first mode set will be detected as a change
    p->dwPos = (DWORD)~0L;	// first pos set will be detected as a change

    // Copy the file or device name into our filename spot.  Set the window
    // caption (if desired).
    lstrcpy(p->achFileName, szFile);
    MCIWndiSetCaption(p);	// wDeviceID must be set before calling this

    // !!! p->wDeviceType = QueryDeviceTypeMCI(p->wDeviceID);

    p->fAllowCopy = TRUE;	// until recorded into

    // Now set the playback window to be our MCI window
    p->fCanWindow = MCIWndString(p, FALSE, szWindowHandle, (UINT_PTR)p->hwnd) == 0;

    if (p->fCanWindow)
        MCIWndGetDest(p->hwnd, &p->rcNormal);
    else
	SetRect(&p->rcNormal, 0, 0, 0, 0);

    // Find out if the device supports palettes.
    p->fHasPalette = MCIWndString(p, FALSE, szStatusPalette) == 0;

    //
    // Now find out the capabilities of this device
    //

// !!! What about these ???
// MCI_GETDEVCAPS_DEVICE_TYPE      0x00000004L
// MCI_GETDEVCAPS_COMPOUND_DEVICE  0x00000006L

    // Find out if the device can record
    p->fCanRecord = MCIWndDevCaps(p, MCI_GETDEVCAPS_CAN_RECORD);

    // Find out if the device can play
    p->fCanPlay = MCIWndDevCaps(p, MCI_GETDEVCAPS_CAN_PLAY);

    // Find out if the device can save
    p->fCanSave = MCIWndDevCaps(p, MCI_GETDEVCAPS_CAN_SAVE);

    // Find out if the device can eject
    p->fCanEject = MCIWndDevCaps(p, MCI_GETDEVCAPS_CAN_EJECT);

    // Find out if the device is file based
    p->fUsesFiles = MCIWndDevCaps(p, MCI_GETDEVCAPS_USES_FILES);

    // Find out if the device has video
    p->fVideo = MCIWndDevCaps(p, MCI_GETDEVCAPS_HAS_VIDEO);

    // Find out if the device has video
    p->fAudio = MCIWndDevCaps(p, MCI_GETDEVCAPS_HAS_AUDIO);

    // Find out if the device can configure
    p->fCanConfig = (MCIWndString(p, FALSE, szConfigureTest) == 0);

    //
    //
    //

    // Now see if we support speed - try normal, half, and max
    p->fSpeed = MCIWndString(p, FALSE, szSetSpeed1000Test) == 0 &&
                MCIWndString(p, FALSE, szSetSpeed500Test) == 0 &&
                MCIWndString(p, FALSE, szSetSpeedTest, SPEED_MAX * 10) == 0;

    // Now see if we support volume - try normal, mute, and max
    p->fVolume = MCIWndString(p, FALSE, szSetVolumeTest, VOLUME_MAX * 5) ==0 &&
                 MCIWndString(p, FALSE, szSetVolume0Test) == 0;
    p->wMaxVol = 100;
    // If someone happens to support double volume, let's give it to them.
    if (MCIWndString(p, FALSE, szSetVolumeTest, VOLUME_MAX * 10) == 0)
	p->wMaxVol = 200;

    // See if the device would support tmsf mode.  If so, use milliseconds mode
    // and later on we'll fake knowing where tracks begin and end
    p->fHasTracks = (MCIWndString(p, FALSE, szSetFormatTMSF) == 0);
    if (p->fHasTracks) {
        dw = MCIWndString(p, FALSE, szSetFormatMS);
 	if (dw != 0)
	    p->fHasTracks = FALSE;
    }

    if (!p->fHasTracks) {
        // Force us into a reasonable time format
        dw = MCIWndString(p, FALSE, szSetFormatFrames);
        if (dw != 0)
	    dw = MCIWndString(p, FALSE, szSetFormatMS);
        if (dw != 0)
	    ;		// !!! What to do? Don't turn playbar off without
    }	 		// !!! destroying it...

    // Set the media length and trackbar range
    MCIWndiValidateMedia(p);

    // Fix the toolbar buttons for the new device
    MCIWndiFixMyPlaybar(p);

    // Make an appropriate menu for this device
    MCIWndiMakeMeAMenu(p);

    // We need a TIMER to notify the "owner" when things change
    MCIWndiSetTimer(p);

    // Set the size of the movie (and maybe the window) and re-draw new toolbar
    MCIWndiSize(p, p->iZoom);

#if 0 // We need the focus on our main window to get key accelerators
    // Bring focus to the thumb so caret will flash
    // I know the WM_SETFOCUS msg does this, but it seems to need to happen here
    // too.
    if (p->hwndTrackbar && GetFocus() == p->hwnd)
	SetFocus(p->hwndTrackbar);
#endif

    // We need to notify our "owner" that we've opened a new file
    // !!! This flag can have more than one bit set so the test is different
    if (p->dwStyle & MCIWNDF_NOTIFYMEDIA & ~MCIWNDF_NOTIFYANSI) {
#ifdef UNICODE
        if (p->dwStyle & MCIWNDF_NOTIFYANSI) {

            char * lpA;
            int sz;
            sz = lstrlen(szFile) + 1;
            lpA = (char *)LocalAlloc(LPTR, sz * sizeof(char));
            if (lpA) {
	       Iwcstombs(lpA, szFile, sz);
               NotifyOwner(p, MCIWNDM_NOTIFYMEDIA, (WPARAM)p->hwnd,(LPARAM)lpA);
               LocalFree ((HLOCAL)lpA);
            }
        }
        else
#endif
        {
            NotifyOwner(p, MCIWNDM_NOTIFYMEDIA, (WPARAM)p->hwnd,
			(LPARAM)szFile);
        }
    }

    // Make sure the newly opened movie paints in the window now
    InvalidateRect(p->hwnd, NULL, TRUE);

    return 0;	// success
}

STATICFN LONG MCIWndiChangeStyles(PMCIWND p, UINT mask, UINT value)
{
    DWORD	dwOldStyle = p->dwStyle;
    DWORD	dwMaskOff, dwValue, dwChanged;

    //
    // Using the mask, change the appropriate bits in the style
    //
    dwMaskOff = dwOldStyle & (~(DWORD)mask);
    dwValue   = (DWORD)mask & (DWORD)value;
    p->dwStyle = dwMaskOff | dwValue;

    //
    // Which bits changed?
    //
    dwChanged = (dwOldStyle & (DWORD)mask) ^ (dwValue & (DWORD)mask);

    //
    // We changed whether or not we want a menu button or a record button
    // on the playbar
    //
    if (dwChanged & (MCIWNDF_NOMENU | MCIWNDF_NOOPEN | MCIWNDF_RECORD)) {
	MCIWndiMakeMeAMenu(p);	// add/remove record from the menu
	// We have a playbar, so fix it
	if (!(p->dwStyle & MCIWNDF_NOPLAYBAR)) {
	    MCIWndiFixMyPlaybar(p);
	    MCIWndiSize(p, 0);
	}
    }

    //
    // We changed the show/don't show playbar flag!
    //
    if (dwChanged & MCIWNDF_NOPLAYBAR) {

 	// Remove the playbar
	if (p->dwStyle & MCIWNDF_NOPLAYBAR) {
	    DestroyWindow(p->hwndToolbar);
	    DestroyWindow(p->hwndTrackbar);
	    p->hwndToolbar = NULL;
	    p->hwndTrackbar = NULL;	
	    MCIWndiMakeMeAMenu(p);	// since toolbar's gone, menus change

	    if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW)) {
	        // Now resize the window smaller to account for the missing
		// playbar.  Don't touch the movie size.
		MCIWndiSize(p, 0);

	    // If the window isn't being resized, we may still need to grow
	    // the movie size a bit to take up the extra space where the toolbar
	    // vanished. (happens automatically in the previous case)
	    } else if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEMOVIE)) {
		PostMessage(p->hwnd, WM_SIZE, 0, 0L);
	    }	

	// Add a playbar
	} else {
	    MCIWndiMakeMeAPlaybar(p);
	    MCIWndiFixMyPlaybar(p);
	    MCIWndiMakeMeAMenu(p);	// since toolbar's used, menus change

	    if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW)) {
	        // Now resize the window a little bigger to account for the new
		// playbar.  Don't touch the movie size.
		MCIWndiSize(p, 0);

	    // If the window isn't being resized, we may still need to shrink
	    // the movie size because the toolbar covers up some extra space.
	    // (happens automatically in the previous case)
	    } else if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEMOVIE)) {
		PostMessage(p->hwnd, WM_SIZE, 0, 0L);

	    // Irregardless, we need to fix the toolbar
	    } else
		// Put the toolbar in a reasonable spot
		MCIWndiSizePlaybar(p);
	}
    }

    //
    // We changed a SHOW flag and need to reset the caption
    //
    if (dwChanged & MCIWNDF_SHOWALL)
	MCIWndiSetCaption(p);

    //
    // We turned the AUTOSIZEMOVIE flag on and need to resize the device.
    // This happens before AUTOSIZEWINDOW so if both flags are turned on
    // the movie will snap to the window not vice versa.
    // !!! Should we even snap it right now?
    //
    if (dwChanged & MCIWNDF_NOAUTOSIZEMOVIE &&
				!(p->dwStyle & MCIWNDF_NOAUTOSIZEMOVIE))
	PostMessage(p->hwnd, WM_SIZE, 0, 0);

    //
    // We turned the AUTOSIZEWINDOW flag on
    // Snap our window to the current movie size.
    //
    if (dwChanged & MCIWNDF_NOAUTOSIZEWINDOW &&
				!(p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW))
	MCIWndiSize(p, 0);

    DragAcceptFiles(p->hwnd, (p->dwStyle & MCIWNDF_NOMENU | MCIWNDF_NOOPEN) == 0);

    return 0;	// !!! success ?
}


//
// We're about to play.  We might want to seek to the beginning first if we're
// at the end, or seek to the end first if we're at the beginning and playing
// backwards.
//
STATICFN void MCIWndiPlaySeek(PMCIWND p, BOOL fBackwards)
{

    // Playing backwards? If we're at the beginning, seek to the end

    if (fBackwards) {
	if (MCIWndGetPosition(p->hwnd) <= MCIWndGetStart(p->hwnd))
	    MCIWndSeek(p->hwnd, MCIWND_END);
	return;
    }

    // Playing forwards.
    // If we're near the end, rewind before playing
    // Some devices are broken so we can't just test being at the end

    // Frames mode ... last or second to last frame
    if (MCIWndGetTimeFormat(p->hwnd, NULL, 0) == MCI_FORMAT_FRAMES) {
	if (MCIWndGetPosition(p->hwnd) >= MCIWndGetEnd(p->hwnd) - 1)
	    MCIWndSeek(p->hwnd, MCIWND_START);

    // Millisecond mode ... within last 1/4 second
    } else if (MCIWndGetTimeFormat(p->hwnd, NULL, 0) ==
					MCI_FORMAT_MILLISECONDS) {
	if (MCIWndGetEnd(p->hwnd) - MCIWndGetPosition(p->hwnd) < 250)
	    MCIWndSeek(p->hwnd, MCIWND_START);

    // something else ... no hack
    } else {
	if (MCIWndGetPosition(p->hwnd) == MCIWndGetEnd(p->hwnd))
	    MCIWndSeek(p->hwnd, MCIWND_START);
    }
}


//
// Handle our WM_TIMER
//
STATICFN void MCIWndiTimerStuff(PMCIWND p)
{
    DWORD	dwMode;
    DWORD	dwPos;

    //
    // Someone's interested in knowing the mode of the device
    //
    if ((p->dwStyle & MCIWNDF_NOTIFYMODE) ||
		!(p->dwStyle & MCIWNDF_NOPLAYBAR) ||
		(p->dwStyle & MCIWNDF_SHOWMODE)) {

	dwMode = MCIWndGetMode(p->hwnd, NULL, 0);

	//
	// If we haven't set the trackbar range or media length yet
	// because we weren't ready, maybe we can do it now!
	// Also, don't update media until you're done recording.
	//
	if (dwMode != MCI_MODE_NOT_READY && dwMode != MCI_MODE_OPEN &&
		dwMode != MCI_MODE_RECORD && p->fMediaValid == FALSE)
	    MCIWndiValidateMedia(p);

	//
	// No device loaded?  Time to kill our timer
	//
        if (p->wDeviceID == 0)
	    KillTimer(p->hwnd, TIMER1);

	//
	// The mode has changed!
	//
	if (dwMode != p->dwMode) {

	    p->dwMode = dwMode;

	    //
	    // Notify the "owner" of the mode change
	    //
	    if ((p->dwStyle & MCIWNDF_NOTIFYMODE))
		NotifyOwner(p, MCIWNDM_NOTIFYMODE, (WPARAM)p->hwnd, dwMode);

	    //
	    // Set the Window Caption to include the new mode
	    //
	    if ((p->dwStyle & MCIWNDF_SHOWMODE))
		MCIWndiSetCaption(p);

	    //
	    // Fix up the toolbar bitmaps if the mode has changed
	    //
	    MCIWndiPlaybarGraying(p);
	}
    }

    //
    // Someone's interested in knowing the new position
    //
    if (!(p->dwStyle & MCIWNDF_NOPLAYBAR) ||
	 (p->dwStyle & MCIWNDF_NOTIFYPOS) ||
	 (p->dwStyle & MCIWNDF_SHOWPOS)) {

	dwPos = MCIWndGetPosition(p->hwnd);

	//
	// The position has changed!
	//
	if (dwPos != p->dwPos) {

	    //
	    // Make sure start and length haven't changed too (format change) ?
	    //
	    MCIWndiValidateMedia(p);

	    p->dwPos = dwPos;

	    //
	    // Notify the "owner" of the position change
	    //
	    if ((p->dwStyle & MCIWNDF_NOTIFYPOS))
		NotifyOwner(p, MCIWNDM_NOTIFYPOS, (WPARAM)p->hwnd, dwPos);

	    //
	    // Set the Window Caption to include the new position
	    //
	    if ((p->dwStyle & MCIWNDF_SHOWPOS))
		MCIWndiSetCaption(p);

	    //
	    // Update the trackbar to the new position but not while
	    // we're dragging the thumb
	    //
	    if (!(p->dwStyle & MCIWNDF_NOPLAYBAR) && !p->fScrolling && p->dwMode != MCI_MODE_SEEK)
		SendMessage(p->hwndTrackbar, TBM_SETPOS, TRUE, dwPos);
	}
    }
}


STATICFN void MCIWndiDrop(HWND hwnd, WPARAM wParam)
{
    TCHAR       szPath[MAX_PATH];
    UINT	nDropped;

    // Get number of files dropped
    nDropped = DragQueryFile((HANDLE)wParam, (UINT)-1, NULL, 0);

    if (nDropped) {
	SetActiveWindow(hwnd);

	// Get the file that was dropped....
        DragQueryFile((HANDLE)wParam, 0, szPath, NUMELMS(szPath));

	MCIWndOpen(hwnd, szPath, 0);
    }
    DragFinish((HANDLE)wParam);     /* Delete structure alocated */
}

//--------ansi thunk functions ---------------------------------

#ifdef UNICODE

STATICFN LRESULT MCIWndiOpenA(PMCIWND p, WPARAM wFlags, LPCSTR szFile)
{
    WCHAR * lpW;
    int sz;
    LRESULT l;

    if ((szFile == NULL) || (szFile == (LPSTR)-1)) {
	return MCIWndiOpen(p, wFlags, (LPTSTR)szFile);
    }

    sz = lstrlenA(szFile) + 1;
    lpW = (WCHAR *)LocalAlloc(LPTR, sz * sizeof(WCHAR));
    if (!lpW) {
	return -1;
    }
    Imbstowcs(lpW, szFile, sz);

    l = MCIWndiOpen(p, wFlags, lpW);

    LocalFree((HLOCAL)lpW);
    return l;
}


// ansi version of MCIWndGet
STATICFN DWORD MCIWndGetA(PMCIWND p, LPSTR sz, LPSTR szRet, int len, ...)
{
    char    ach[MAX_PATH];
    int     i;
    DWORD   dw;
    va_list va;

    if (!p->wDeviceID) {
	szRet[0] = 0;
	return 0L;
    }

    for (i=0; *sz && *sz != ' '; )
	ach[i++] = *sz++;

    i += wsprintfA(&ach[i], " %d ", (UINT)p->alias);
    va_start(va, len);
    i += wvsprintfA(&ach[i], sz, va);  //!!!use varargs
    va_end(va);

    // initialize to NULL return string
    szRet[0] = 0;

    dw = mciSendStringA(ach, szRet, len, p->hwnd);

    DPF("MCIWndGetA('%s'): '%s'", (LPSTR)ach, (LPSTR)szRet);

    return dw;
}
#endif

/*--------------------------------------------------------------+
| MCIWndCommand - WM_COMMAND processing for MCIWnd class        |                         |
|                                                               |
+--------------------------------------------------------------*/

static LRESULT MCIWndCommands (
   PMCIWND p,         // IN:
   HWND    hwnd,      // IN:
   WPARAM  wParam,    // IN:
   LPARAM  lParam)    // IN:
{
   WORD wID = GET_WM_COMMAND_ID(wParam, lParam);
   HWND hwndCtl = GET_WM_COMMAND_HWND(wParam, lParam);
   WORD wNotify = GET_WM_COMMAND_CMD(wParam, lParam);

   // Check for ZOOM commands
   if (wID >= IDM_MCIZOOM && wID < IDM_MCIZOOM + 1000)
       MCIWndSetZoom(hwnd, wID - IDM_MCIZOOM);

   // !!! Hack from Hell
   // If our bogus top menu item is selected, turn it into the REAL
   // menu item closest to it.
   //
   if (wID == IDM_MCIVOLUME + VOLUME_MAX + 1)
       wID = IDM_MCIVOLUME + p->wMaxVol;
   if (wID == IDM_MCIVOLUME + VOLUME_MAX + 2)
       wID = IDM_MCIVOLUME;

   // VOLUME command? Uncheck old one, reset volume, and check new one
   // Round to the nearest 5 to match a menu identifier
   if (wID >=IDM_MCIVOLUME && wID <=IDM_MCIVOLUME + p->wMaxVol) {
	// 42 means we're auditioning changes as we drag the menu bar, but
	// we don't actually want to remember this value permanently (see
	// OwnerDraw).
        if (MCIWndSetVolume(hwnd, (wID - IDM_MCIVOLUME) * 10) == 0
                               && lParam != 42) {
           CheckMenuItem(p->hmenuVolume, p->uiHack, MF_UNCHECKED);
           CheckMenuItem(p->hmenuVolume, wID, MF_CHECKED);
       }
   }

   // !!! Hack from Hell
   // If our bogus top menu item is selected, turn it into the REAL
   // menu item closest to it.
   if (wID == IDM_MCISPEED + SPEED_MAX + 1)
       wID = IDM_MCISPEED + SPEED_MAX;
   if (wID == IDM_MCISPEED + SPEED_MAX + 2)
       wID = IDM_MCISPEED;

   // SPEED command? Uncheck old one, reset speed, and check new one
   // Round to the nearest 5 to match a menu identifier
   if (wID >=IDM_MCISPEED && wID <= IDM_MCISPEED + SPEED_MAX) {
	// 42 means we're auditioning changes as we drag the menu bar, but
	// we don't actually want to remember this value permanently (see
	// OwnerDraw).
        if (MCIWndSetSpeed(hwnd, (wID - IDM_MCISPEED) * 10) == 0
                               && lParam != 42) {
           CheckMenuItem(p->hmenuSpeed, p->uiHack, MF_UNCHECKED);
           CheckMenuItem(p->hmenuSpeed, wID, MF_CHECKED);
       }
   }

   switch(wID)
   {
       case MCI_RECORD:
           if (GetKeyState(VK_SHIFT) < 0)
           {
               //!!! toggle?
               //MCIWndRecordPreview(hwnd);
           }
           else
           {
                MCIWndRecord(hwnd);
		// would not copy newly recorded stuff anyway
		p->fAllowCopy = FALSE;
		MCIWndiMakeMeAMenu(p);
	
           }
           break;

       //            PLAY = normal play
       //      SHIFT+PLAY = play backward
       //       CTRL+PLAY = play fullscreen
       // SHIFT+CTRL+PLAY = play fullscreen backward
       //
       case MCI_PLAY:

#define MaybeRepeat (p->fRepeat ? (LPTSTR)szRepeat : (LPTSTR)szNULL)

           // NOTE: We never set errors for the repeat play, because
           // lots of device don't support it and would fail.

           if (GetKeyState(VK_SHIFT) < 0)
               // If we're at the beginning, seek to the end.
               MCIWndiPlaySeek(p, TRUE);
           else
               // If we're at the end, seek to the beginning.
               MCIWndiPlaySeek(p, FALSE);

           if (GetKeyState(VK_CONTROL) < 0)
           {
               if (GetKeyState(VK_SHIFT) < 0) {
                   if (MCIWndString(p, FALSE, szPlayFullscreenReverse,
                                                       MaybeRepeat))
                       MCIWndString(p, TRUE, szPlayFullscreenReverse,
                                                       (LPTSTR)szNULL);
               } else {
                   if (MCIWndString(p, FALSE, szPlayFullscreen,
                                                       MaybeRepeat))
                       MCIWndString(p, TRUE, szPlayFullscreen,
                                                       (LPTSTR)szNULL);
               }

           } else if (GetKeyState(VK_SHIFT) < 0) {
               if (MCIWndString(p, FALSE, szPlayReverse, MaybeRepeat))
                   MCIWndString(p, TRUE, szPlayReverse, (LPTSTR)szNULL);
           } else {
               if (MCIWndString(p, FALSE, szPlay, MaybeRepeat))
                   MCIWndString(p, TRUE, szPlay, (LPTSTR)szNULL);
           }

           // Kick ourselves to fix up toolbar since mode changed
           MCIWndiTimerStuff(p);

           break;

       case MCI_STOP:
           return MCIWndStop(hwnd);

       case MCI_PAUSE:
           return MCIWndPause(hwnd);

       case IDM_MCINEW:
           return MCIWndNew(hwnd, NULL);

       case IDM_MCIOPEN:
           return MCIWndOpenDialog(hwnd);

       case MCI_SAVE:
           return MCIWndSaveDialog(hwnd);

       case IDM_MCICLOSE:
           return MCIWndClose(hwnd);

       case IDM_MCICONFIG:
           MCIWndString(p, TRUE, szConfigure);

           // AVI's configure box might change the size (zoom by 2)
           // so we better call our size routine.
           MCIWndiSize(p, 0);

           // Taking ZOOM X 2 off might leave the outside not painted
           InvalidateRect(hwnd, NULL, TRUE);
           break;

       case IDM_MCICOMMAND:
           mciDialog(hwnd);

           // call mciwndisize?
           break;

       case IDM_COPY:
           MCIWndCopy(p);
           break;

       case IDM_MCIREWIND:
           return MCIWndSeek(hwnd, MCIWND_START);

       case IDM_MCIEJECT:
           return MCIWndEject(hwnd);

// When somebody presses a toolbar button in 16 bit Chicago, we are told
// about it via a WM_COMMAND.  32 bit Chicago and NT uses WM_NOTIFY, handled
// elsewhere.
#ifndef _WIN32
       case ID_TOOLBAR: {
           RECT rc;
           RECT rcT;
           MSG  msgT;

	   // We're only interested in a pressing of the Menu button
           if (wNotify != TBN_BEGINDRAG ||
               (UINT)hwndCtl != IDM_MENU ||
               !SendMessage(p->hwndToolbar, TB_ISBUTTONENABLED,
                       IDM_MENU, 0) ||
               !p->hmenu)
           break;

           SendMessage(p->hwndToolbar, TB_GETITEMRECT,
               (int)SendMessage(p->hwndToolbar, TB_COMMANDTOINDEX,
                       IDM_MENU, 0),
               (LPARAM)(LPVOID)&rc);
           rcT = rc;
           ClientToScreen(p->hwndToolbar, (LPPOINT)&rc);
           ClientToScreen(p->hwndToolbar, (LPPOINT)&rc + 1);

           // Push the button down (accelerator won't have done this)
           SendMessage(p->hwndToolbar, TB_PRESSBUTTON, IDM_MENU,
               TRUE);

           // Don't allow error dlgs to come up while we're tracking.
           // That would cause windows to shatter and send shrapnel
           // flying.
           p->fTracking = TRUE;
           TrackPopupMenu(p->hmenu, 0, rc.left, rc.bottom - 1, 0,
                       hwnd, &rc);  // don't dismiss menu inside button
           p->fTracking = FALSE;

           // Bring the button back up.
           SendMessage(p->hwndToolbar, TB_PRESSBUTTON, IDM_MENU,
               FALSE);

           // What if we press the menu button to make the menu go
           // away?  It's just going to bring the menu back up again!
           // So we need to pull the click out of the queue.
           // There are bugs in the toolbar code to prevent me from
           // doing this any other way (like disabling the button)
           if (PeekMessage(&msgT, p->hwndToolbar, WM_LBUTTONDOWN,
                               WM_LBUTTONDOWN, PM_NOREMOVE)) {
#ifdef _WIN32
               POINT pt = {MAKEPOINTS(msgT.lParam).x,
                           MAKEPOINTS(msgT.lParam).y };
#else
               POINT pt = MAKEPOINT(msgT.lParam);
#endif
               if (PtInRect(&rcT, pt))
                   PeekMessage(&msgT, p->hwndToolbar, WM_LBUTTONDOWN,
                               WM_LBUTTONDOWN, PM_REMOVE);
           }
           break;
       }
#endif

       default:
           break;
   }

   return 0;
}


/*--------------------------------------------------------------+
| SubClassedTrackbarWndProc - eat key presses that do something |
| 	special so trackbar never sees them.                    |
+--------------------------------------------------------------*/
LRESULT CALLBACK _LOADDS SubClassedTrackbarWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg) {
	case WM_KEYDOWN:
	case WM_KEYUP:
	case WM_SYSCHAR:
	    return SendMessage(GetParent(hwnd), msg, wParam, lParam);
    }

    return CallWindowProc(fnTrackbarWndProc, hwnd, msg, wParam, lParam);
}


/*--------------------------------------------------------------+
| MCIWndProc - MCI window's window proc                         |
|                                                               |
+--------------------------------------------------------------*/
LRESULT CALLBACK _LOADDS MCIWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    PMCIWND             p;
    DWORD	    	dw;
    HDC		    	hdc;
    PAINTSTRUCT	    	ps;
    DWORD               dwPos;
    POINT		pt;
    MINMAXINFO FAR 	*lpmmi;
    RECT		rc;
    BOOL                f;
    TCHAR               ach[80];
    MCI_GENERIC_PARMS   mciGeneric;
    LPRECT              prc;
    int			i;
    HWND		hwndD;

    p = (PMCIWND)(UINT)GetWindowLong(hwnd, 0);

    switch(msg){
        case WM_CREATE:
            if (!MCIWndiCreate(hwnd, lParam))
                return -1;

            break;

        // Make the trackbar background BTNFACE to match the colour scheme
#ifdef _WIN32
	case WM_CTLCOLORBTN:
	case WM_CTLCOLORDLG:
	case WM_CTLCOLOREDIT:
	case WM_CTLCOLORLISTBOX:
	case WM_CTLCOLORMSGBOX:
	case WM_CTLCOLORSCROLLBAR:
	case WM_CTLCOLORSTATIC:
    	    if ((HWND)lParam == p->hwndTrackbar) {
#else
        case WM_CTLCOLOR:
            if ((HWND)LOWORD(lParam) == p->hwndTrackbar) {
#endif
            //  return (LRESULT)(UINT)GetStockObject(LTGRAY_BRUSH);
		SetBkColor((HDC)wParam, GetSysColor(COLOR_BTNFACE));
		SetTextColor((HDC)wParam, GetSysColor(COLOR_BTNTEXT));
                return (LRESULT)(UINT_PTR)GetSysColorBrush(COLOR_BTNFACE);
            }
            break;

	case MCI_SAVE:
	    // wParam presently unused and not given by the macro
            return MCIWndiSave(p, wParam, (LPTSTR)lParam);

#ifdef _WIN32
        case MCIWNDM_OPEN:
#else
        case MCI_OPEN:
#endif
            return MCIWndiOpen(p, wParam, (LPTSTR)lParam);

#ifdef UNICODE
        // ansi thunk for above
        case MCIWNDM_OPENA:
            return MCIWndiOpenA(p, wParam, (LPSTR)lParam);
#endif

	case MCIWNDM_NEW:
	    return MCIWndiOpen(p, MCIWNDOPENF_NEW, (LPTSTR)lParam);

#ifdef UNICODE
	//ansi thunk for above
	case MCIWNDM_NEWA:
	    return MCIWndiOpenA(p, MCIWNDOPENF_NEW, (LPSTR) lParam);
#endif
	
        case MCI_PLAY:

	    if (!p->wDeviceID)
		return 0;
	    // Seek to the beginning if we're near the end
	    MCIWndiPlaySeek(p, FALSE);

	case MCI_STOP:
	case MCI_PAUSE:
	case MCI_RESUME:
	case MCI_RECORD:

	    dw = 0;
	    // Report/Show errors for this
	    if (p->wDeviceID) {
		// buggy drivers crash if we pass a null parms address
	        dw = mciSendCommand(p->wDeviceID, msg, 0,
			(DWORD_PTR)(LPVOID)&mciGeneric);
		if (dw == 0 && msg == MCI_RECORD) {
		    // RECORDING dirties MCIWAVE and what we would copy would
		    // still be the old file, so just disable it to save grief.
		    p->fAllowCopy = FALSE;
		    MCIWndiMakeMeAMenu(p);
		}
		MCIWndiHandleError(p, dw);
		// kick ourselves to show new Mode
		MCIWndiTimerStuff(p);
	    }
	    return dw;

	case MCIWNDM_PLAYREVERSE:

	    if (!p->wDeviceID)
		return 0;
	    // Seek to the end if we're near the beginning
	    MCIWndiPlaySeek(p, TRUE);
            dw = MCIWndString(p, TRUE, szPlayReverse, (LPTSTR)szNULL);
	    // kick ourselves to show new Mode
	    MCIWndiTimerStuff(p);
	    return dw;

        case MCI_CLOSE:
	    if (lParam)
		// We delayed the closing of the MCI device because the MCI
		// device may have hooked our window proc and be on our stack
		// and killing it before would have destroyed the universe.
		// buggy drivers crash if we pass a null parms address
        	return mciSendCommand((UINT)lParam, MCI_CLOSE, 0,
			(DWORD_PTR)(LPVOID)&mciGeneric);
		
	    else
	        // Do all the stuff for closing
	        return MCIWndiClose(p, TRUE);

	case MCIWNDM_EJECT:
	    return MCIWndString(p, TRUE, szSetDoorOpen);

	case MCIWNDM_PLAYFROM:
	    if (lParam == MCIWND_START)
	        dw = MCIWndString(p, TRUE, szPlayFrom, MCIWndGetStart(hwnd));
	    else
	        dw = MCIWndString(p, TRUE, szPlayFrom, (LONG)lParam);
	    MCIWndiTimerStuff(p);	// kick ourselves to see mode change
	    return dw;

	case MCIWNDM_PLAYTO:
	    if (lParam == MCIWND_END)
	        dw = MCIWndString(p, TRUE, szPlayTo, MCIWndGetEnd(hwnd));
	    else if (lParam == MCIWND_START)
	        dw = MCIWndString(p, TRUE, szPlayTo, MCIWndGetStart(hwnd));
	    else
	        dw = MCIWndString(p, TRUE, szPlayTo, (LONG)lParam);
	    MCIWndiTimerStuff(p);	// kick ourselves to see mode change
	    return dw;

	case MCI_STEP:
            return MCIWndString(p, TRUE, szStep, (LONG)lParam);

	case MCI_SEEK:
	    if (lParam == MCIWND_START)
                return MCIWndString(p, TRUE, szSeek, MCIWndGetStart(hwnd));
	    else if (lParam == MCIWND_END)
                return MCIWndString(p, TRUE, szSeek, MCIWndGetEnd(hwnd));
	    else
                return MCIWndString(p, TRUE, szSeek, (LONG)lParam);

	case MCIWNDM_SETREPEAT:
	    p->fRepeat = (BOOL)lParam;
	    return 0;

	case MCIWNDM_GETREPEAT:
	    return p->fRepeat;

	case MCIWNDM_GETDEVICEID:
	    return p->wDeviceID;

	case MCIWNDM_GETALIAS:
	    return p->alias;

	case MCIWNDM_GETMODE:
            if (lParam)
                MCIWndGet(p, szStatusMode, (LPTSTR)lParam, (UINT)wParam);
	    return MCIWndStatus(p, MCI_STATUS_MODE, MCI_MODE_NOT_READY);

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_GETMODEA:
    	    if (lParam) {
		MCIWndGetA(p, szStatusModeA, (LPSTR)lParam, (UINT)wParam);
	    }
	    return MCIWndStatus(p, MCI_STATUS_MODE, MCI_MODE_NOT_READY);
#endif

	// Return the position as a string if they give us a buffer
	case MCIWNDM_GETPOSITION:
#ifdef UNICODE
	case MCIWNDM_GETPOSITIONA:
#endif
            if (lParam) {
		// If we can do tracks, let's give them a pretty string
		if (p->fHasTracks)
        	    MCIWndString(p, FALSE, szSetFormatTMSF);
#ifdef UNICODE
		if (msg == MCIWNDM_GETPOSITIONA) {
		    MCIWndGetA(p, szStatusPositionA,
			(LPSTR)lParam,(UINT)wParam);
		}
		else
#endif
		{
		    MCIWndGet(p, szStatusPosition,
			(LPTSTR)lParam,(UINT)wParam);
		}

		if (p->fHasTracks)
        	    MCIWndString(p, FALSE, szSetFormatMS);
	    }
	    return MCIWndStatus(p, MCI_STATUS_POSITION, 0);


	case MCIWNDM_GETSTART:
	    // Start is a command that works differently
            return MCIWndGetValue(p, FALSE, szStatusStart, 0);

	case MCIWNDM_GETLENGTH:
	    return MCIWndStatus(p, MCI_STATUS_LENGTH, 0);

	case MCIWNDM_GETEND:
	    return MCIWndGetStart(hwnd) + MCIWndGetLength(hwnd);

        case MCIWNDM_SETZOOM:
            p->iZoom = (int)lParam;
	    MCIWndiSize(p, (int)lParam);
            return 0;

        case MCIWNDM_GETZOOM:
            return p->iZoom ? p->iZoom : 100;

        case MCIWNDM_GETPALETTE:
            return MCIWndGetValue(p, FALSE, szStatusPalette, 0);

        case MCIWNDM_SETPALETTE:
            return MCIWndString(p, TRUE, szSetPalette, (HPALETTE)wParam);

	//
	// Returns our error code
	//
	case MCIWNDM_GETERROR:
	    if (lParam) {
                mciGetErrorString(p->dwError, (LPTSTR)lParam, (UINT)wParam);
	    }
	    dw = p->dwError;
	//    p->dwError = 0L;	// we never clear the error
	    return dw;

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_GETERRORA:
	    if (lParam) {
		mciGetErrorStringA(p->dwError, (LPSTR)lParam, (UINT)wParam);
	    }
	    return p->dwError;
#endif

	case MCIWNDM_GETFILENAME:
	    if (lParam)
                lstrcpyn((LPTSTR)lParam, p->achFileName, (UINT)wParam);
            return (lParam == 0);    // !!!

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_GETFILENAMEA:
            if (lParam) {
		Iwcstombs((LPSTR)lParam, p->achFileName, (UINT)wParam);
	    }
	    return (lParam == 0);
#endif

	case MCIWNDM_GETDEVICE:
	    if (lParam)
                return MCIWndGet(p, szSysInfo, (LPTSTR)lParam,
		    (UINT)wParam);
	    return 42;	// !!!

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_GETDEVICEA:
	    if (lParam) {
		return MCIWndGetA(p, szSysInfoA, (LPSTR)lParam, (UINT)wParam);
	    } else {
		return 42; // why do they do this??
	    }
#endif


	case MCIWNDM_SETVOLUME:
	    // Uncheck the current volume, and check the new one.
	    // Round to nearest 5 so it matches a menu item identifier
            i = ((int)MCIWndGetValue(p, FALSE, szStatusVolume, 1000) / 50) * 5;
	    if (p->hmenuVolume)
                CheckMenuItem(p->hmenuVolume, IDM_MCIVOLUME + i, MF_UNCHECKED);
            dw = MCIWndString(p, TRUE, szSetVolume, (int)lParam);
            i = ((int)lParam / 50) * 5;
	    if (p->hmenuVolume)
                CheckMenuItem(p->hmenuVolume, IDM_MCIVOLUME + i, MF_CHECKED);
	    return dw;

	case MCIWNDM_GETVOLUME:
	    return MCIWndGetValue(p, FALSE, szStatusVolume, 1000);

	case MCIWNDM_SETSPEED:
	    // Uncheck the current speed, and check the new one.
	    // Round to nearest 5 so it matches a menu item identifier
            i = ((int)MCIWndGetValue(p, FALSE, szStatusSpeed, 1000) / 50) * 5;
	    if (p->hmenuSpeed)
                CheckMenuItem(p->hmenuSpeed, IDM_MCISPEED + i, MF_UNCHECKED);
            dw = MCIWndString(p, TRUE, szSetSpeed, (int)lParam);
            i = ((int)lParam / 50) * 5;
	    if (p->hmenuSpeed)
                CheckMenuItem(p->hmenuSpeed, IDM_MCISPEED + i, MF_CHECKED);
	    return dw;

	case MCIWNDM_GETSPEED:
            return MCIWndGetValue(p, FALSE, szStatusSpeed, 1000);

	case MCIWNDM_SETTIMEFORMAT:
            dw = MCIWndString(p, TRUE, szSetFormat, (LPTSTR)lParam);
	    MCIWndiValidateMedia(p);
	    return dw;

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_SETTIMEFORMATA:
	{
	    WCHAR * lpW;
	    int sz = lstrlenA( (LPSTR) lParam) + 1;
	    lpW = (WCHAR *) LocalAlloc(LPTR, sz * sizeof(WCHAR));
            if (lpW) {
                Imbstowcs(lpW, (LPSTR) lParam, sz);
		dw = MCIWndString(p, TRUE, szSetFormat, (LPTSTR)lpW);
		MCIWndiValidateMedia(p);
		LocalFree((HLOCAL)lpW);
	    } else {
		dw = MCIERR_OUT_OF_MEMORY;
	    }
	    return dw;
	}
#endif


	case MCIWNDM_GETTIMEFORMAT:
	    if (lParam)
                MCIWndGet(p, szStatusFormat, (LPTSTR)lParam, (UINT)wParam);
	    return MCIWndStatus(p, MCI_STATUS_TIME_FORMAT, 0);	// !!!

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_GETTIMEFORMATA:
	    if (lParam) {
		MCIWndGetA(p, szStatusFormatA, (LPSTR) lParam, (UINT)wParam);
	    }
	    return MCIWndStatus(p, MCI_STATUS_TIME_FORMAT, 0);
#endif


	case MCIWNDM_VALIDATEMEDIA:
	    MCIWndiValidateMedia(p);
	    break;

 	case MCIWNDM_GETSTYLES:
	    return (UINT)(p->dwStyle & 0xFFFF);

 	case MCIWNDM_CHANGESTYLES:
	    return MCIWndiChangeStyles(p, (UINT)wParam, (UINT)lParam);

        case MCIWNDM_SETACTIVETIMER:
	    if (wParam)
	        p->iActiveTimerRate = (unsigned)wParam;
	
	    if (p->fActive) {
                KillTimer(hwnd, TIMER1);
                MCIWndiSetTimer(p);
	    }
	    break;

        case MCIWNDM_SETINACTIVETIMER:
	    if (wParam)
	        p->iInactiveTimerRate = (unsigned)wParam;
	
	    if (!p->fActive) {
                KillTimer(hwnd, TIMER1);
                MCIWndiSetTimer(p);
	    }
	    break;

        case MCIWNDM_SETTIMERS:
	    if (wParam)
	        p->iActiveTimerRate = (unsigned)wParam;
	    if (lParam)
                p->iInactiveTimerRate = (unsigned)lParam;

            KillTimer(hwnd, TIMER1);
            MCIWndiSetTimer(p);

	    break;

	case MCIWNDM_GETACTIVETIMER:
	    return p->iActiveTimerRate;

	case MCIWNDM_GETINACTIVETIMER:
	    return p->iInactiveTimerRate;

        case MCIWNDM_SENDSTRING:
	    //
	    // App wants to send a string command.

	    // special case the CLOSE command to do our clean up
	    lstrcpyn(ach, (LPTSTR)lParam, lstrlen(szClose) + 1);
            if (lstrcmpi((LPTSTR)ach, szClose) == 0)
		return MCIWndClose(hwnd);

	    // Always sets/clears our error code
            dw = MCIWndGet(p, (LPTSTR)lParam, p->achReturn,NUMELMS(p->achReturn));
	    MCIWndiHandleError(p, dw);
	    // kick ourselves in case mode changed from this command
	    MCIWndiTimerStuff(p);
            return dw;

#ifdef UNICODE
	// ansi thunk for above
	// convert string and then re-send message rather than using
	// MCIWndGetA since we need to store the error string in UNICODE.
	case MCIWNDM_SENDSTRINGA:
	{
            WCHAR * lpW;
            int sz = lstrlenA( (LPSTR) lParam) + 1;
            lpW = (WCHAR *)LocalAlloc(LPTR, sz * sizeof(WCHAR));
            if (lpW) {
                Imbstowcs(lpW, (LPSTR) lParam, sz);

		dw = (DWORD) MCIWndProc(hwnd, MCIWNDM_SENDSTRING, 0,
                        (LPARAM) (LPTSTR)lpW);

		LocalFree((HLOCAL)lpW);
	    } else {
		dw = MCIERR_OUT_OF_MEMORY;
	    }
	    return dw;
	}
#endif

	// Gets the return string from the most recent MCIWndSendString()
        case MCIWNDM_RETURNSTRING:
	    if (lParam)
                lstrcpyn((LPTSTR)lParam, p->achReturn, (DWORD) wParam);
            return (lParam == 0);    // !!!

#ifdef UNICODE
	// ansi thunk for above
	case MCIWNDM_RETURNSTRINGA:
            if (lParam) {
		Iwcstombs((LPSTR) lParam, p->achReturn, (DWORD) wParam);
	    }
	    return (lParam == 0);
#endif

        case MCIWNDM_REALIZE:
	    // buggy drivers crash if we pass a null parms address
            dw = mciSendCommand(p->wDeviceID, MCI_REALIZE,
                (BOOL)wParam ? MCI_ANIM_REALIZE_BKGD : MCI_ANIM_REALIZE_NORM,
		(DWORD_PTR)(LPVOID)&mciGeneric);
            break;

        case MCIWNDM_GET_SOURCE:
	    MCIWndRect(p, (LPRECT)lParam, TRUE);
	    return 0L;

	case MCIWNDM_GET_DEST:
	    MCIWndRect(p, (LPRECT)lParam, FALSE);
	    return 0L;

        case MCIWNDM_PUT_SOURCE:
            prc = (LPRECT)lParam;

            return MCIWndString(p, FALSE, szPutSource,
		  prc->left, prc->top,
		  prc->right - prc->left,
                  prc->bottom - prc->top);

	case MCIWNDM_PUT_DEST:
	    prc = (LPRECT)lParam;

	    return MCIWndString(p, FALSE, szPutDest,
		  prc->left, prc->top,
		  prc->right - prc->left,
                  prc->bottom - prc->top);

        case MCIWNDM_CAN_PLAY:   return p->fCanPlay;
        case MCIWNDM_CAN_WINDOW: return p->fCanWindow;
        case MCIWNDM_CAN_RECORD: return p->fCanRecord;
        case MCIWNDM_CAN_SAVE:   return p->fCanSave;
        case MCIWNDM_CAN_EJECT:  return p->fCanEject;
        case MCIWNDM_CAN_CONFIG: return p->fCanConfig;

	case WM_TIMER:

	    // This timer means we've moved the mouse off of the menu and need
	    // to snap the thumb back to the original value
	    if (wParam == TIMER2) {
		KillTimer(hwnd, TIMER2);

		// If only this would cause OwnerDraw to execute, we could see
		// the thumb bounce back to it's default place.  Alas, no can do
		//CheckMenuItem(p->hmenuHack, p->uiHack, MF_UNCHECKED);
		//CheckMenuItem(p->hmenuHack, p->uiHack, MF_CHECKED);

		// This code will at least set the parameter back even though
		// the thumb won't physically move.
		if (p->hmenuHack == p->hmenuVolume)
		    MCIWndSetVolume(hwnd, (p->uiHack - IDM_MCIVOLUME) * 10);
		else
		    MCIWndSetSpeed(hwnd, (p->uiHack - IDM_MCISPEED) * 10);
	    }

	    //
	    // This is not our timer. Bail.
	    //
	    if (wParam != TIMER1)
		break;

	    MCIWndiTimerStuff(p);

	    break;

	case WM_GETMINMAXINFO:

            // bug fix?
            //
            if (!p)
                break;

	    // We don't want anybody messing with the window size
	    if (p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW)
		break;

	    // do we have a playbar?
	    f = !(p->dwStyle & MCIWNDF_NOPLAYBAR);

            lpmmi = (MINMAXINFO FAR *)(lParam);
            SetRect(&rc, 0, 0, SMALLEST_WIDTH, f ? TB_HEIGHT : 0);
            AdjustWindowRect(&rc, GetWindowLong(hwnd, GWL_STYLE), FALSE);
            lpmmi->ptMinTrackSize.y = rc.bottom - rc.top;
            lpmmi->ptMinTrackSize.x = rc.right - rc.left;

            if (!(p->wDeviceID) || !(p->fCanWindow))
                    lpmmi->ptMaxTrackSize.y = lpmmi->ptMinTrackSize.y;
            break;

        case WM_SIZE:

	    GetClientRect(hwnd, &rc);

	    if (!IsIconic(hwnd)) {
		// if we have a playbar, fix it up to the new size
                f = !(p->dwStyle & MCIWNDF_NOPLAYBAR);

                if (f) {
                    MCIWndiSizePlaybar(p);
                    rc.bottom -= TB_HEIGHT;
                }

                if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEMOVIE))
                    MCIWndString(p, FALSE, szPutDest, 0,0, rc.right, rc.bottom);

	    } else {
                if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEMOVIE))
                    MCIWndString(p, FALSE, szPutDest, 0,0, rc.right, rc.bottom);
	    }

	    // We need to notify the "owner" that our size changed.  Watch for
	    // excessive recursion, which happens with the VfW 1.1d MCIPUZZL
	    // sample app.  That sample's NOTIFYSIZE handler resizes the owner
	    // window as a function of the MCI window size using
	    // AdjustWindowRect().  AdjustWindowRect() used to have a bug which
	    // MCIPUZZL adjusted for by adding 1 to the window height.
	    // Since the AdjustWindowRect() bug has been fixed, that sample
	    // would recurse infinitely because it will size the owner window
	    // one larger which will then resize the MCIWnd one larger which
	    // will then notify the owner which will resize the owner wnd one
	    // larger, etc...
	    if (p->cOnSizeReentered < 3) {
		p->cOnSizeReentered++;
		if (p->dwStyle & MCIWNDF_NOTIFYSIZE) {
		    NotifyOwner(p, MCIWNDM_NOTIFYSIZE, (WPARAM)p->hwnd, (LPARAM)NULL);
		}
		p->cOnSizeReentered--;
	    }
	    break;

        case WM_RBUTTONDOWN:
        case WM_NCRBUTTONDOWN:
        case WM_PARENTNOTIFY:

	    // If we haven't got a menu, or we don't want it, bail
            if (!p->hmenu || p->dwStyle & MCIWNDF_NOMENU)
                break;

	    // If this is not a right button down, bail
            if (msg == WM_PARENTNOTIFY && wParam != WM_RBUTTONDOWN)
                break;

	    GetCursorPos(&pt);

	    // Don't allow error dlgs to come up while we're tracking.  That
	    // would cause windows to enter the twilight zone.
	    p->fTracking = TRUE;
	    TrackPopupMenu(p->hmenu,
		TPM_RIGHTBUTTON, pt.x, pt.y, 0, hwnd, NULL);
	    p->fTracking = FALSE;

            return 0;

        case WM_PALETTECHANGED:
	    if ((HWND)wParam != hwnd && p->fHasPalette)
		InvalidateRect(hwnd, NULL, FALSE);
	    break;

	case WM_QUERYNEWPALETTE:
	    if (p->fHasPalette)
		MCIWndRealize(hwnd, FALSE);
            break;

	// Send a WM_PALETTECHANGED to everyone in the system.  We need to do
	// this manually sometimes because of GDI.
	case MCIWNDM_PALETTEKICK:

	    hwndD = GetDesktopWindow();	// tell everyone DESKTOP changed it
            PostMessage((HWND)-1, WM_PALETTECHANGED, (WPARAM)hwndD, 0);

	    // DESKTOP won't repaint if we give it it's own HWND, so pick a
	    // random window and PRAY it'll stay valid.
	    hwndD = GetActiveWindow();
	    hwndD = GetWindow(hwndD, GW_HWNDLAST);
            PostMessage(GetDesktopWindow(), WM_PALETTECHANGED, (WPARAM)hwndD,0);
	    return 0;

	case MCIWNDM_OPENINTERFACE:
	    wsprintf(ach, szInterface, lParam);
            return MCIWndiOpen(p, 0, (LPTSTR)ach);

	case MCIWNDM_SETOWNER:
	    p->hwndOwner = (HWND)wParam;
	    return 0;

        case WM_ERASEBKGND:
            if (p->fCanWindow) {
                MCIWndRect(p, &rc, FALSE);
                SaveDC((HDC)wParam);
                ExcludeClipRect((HDC)wParam, rc.left, rc.top, rc.right,
                    rc.bottom);
                DefWindowProc(hwnd, msg, wParam, lParam);
                RestoreDC((HDC)wParam, -1);
                return 0;
            }
            break;

        case WM_PAINT:
	    hdc = BeginPaint(hwnd, &ps);
            if (p->wDeviceID && p->fCanWindow)
            {
                MCI_ANIM_UPDATE_PARMS mciUpdate;
		
                mciUpdate.hDC = hdc;

                dw = mciSendCommand(p->wDeviceID, MCI_UPDATE,
                            MCI_ANIM_UPDATE_HDC | MCI_WAIT |
                            MCI_DGV_UPDATE_PAINT,
                            (DWORD_PTR)(LPVOID)&mciUpdate);

                if (dw != 0) /* if the update fails then erase */
                    DefWindowProc(hwnd, WM_ERASEBKGND, (WPARAM)hdc, 0);
	    } else if (IsIconic(hwnd)) {
		DefWindowProc(hwnd, WM_ICONERASEBKGND, (WPARAM)hdc, 0);
		DrawIcon(ps.hdc, 0, 0, p->hicon);
	    }
	    EndPaint(hwnd, &ps);
	    break;
	
	case WM_KEYDOWN:
		switch(wParam) {
		    case VK_LEFT:
			SendMessage(hwnd, WM_HSCROLL, TB_LINEUP, 0); return 0;
		    case VK_RIGHT:
			SendMessage(hwnd, WM_HSCROLL, TB_LINEDOWN, 0); return 0;
		    case VK_PRIOR:
			SendMessage(hwnd, WM_HSCROLL, TB_PAGEUP, 0); return 0;
		    case VK_NEXT:
			SendMessage(hwnd, WM_HSCROLL, TB_PAGEDOWN, 0); return 0;
		    case VK_HOME:
			SendMessage(hwnd, WM_HSCROLL, TB_TOP, 0); return 0;
		    case VK_END:
			SendMessage(hwnd, WM_HSCROLL, TB_BOTTOM, 0); return 0;

		    case VK_UP:
		    case VK_DOWN:
			dw = MCIWndGetValue(p, FALSE, szStatusVolume, 1000);
			if (wParam == VK_UP)
			    i = min((int)p->wMaxVol * 10, (int) dw + 100);
			else
			    i = max(0, (int) dw - 100);
			
			MCIWndSetVolume(p->hwnd, i);
			return 0;	// break will ding
		    default:
			break;
		}
	    break;


	case WM_KEYUP:
	    switch(wParam) {
		case VK_LEFT:
		case VK_RIGHT:
		case VK_PRIOR:
		case VK_NEXT:
		case VK_HOME:
		case VK_END:
		    if (p->fScrolling)
		        SendMessage(hwnd, WM_HSCROLL, TB_ENDTRACK, 0);
		    return 0;	// break will ding
                case VK_ESCAPE:
                    MCIWndStop(hwnd);
                    return 0;
		default:
		    break;
	    }
			
    	    if (GetKeyState(VK_CONTROL) & 0x8000) {
		switch(wParam) {
		    case '1':
		    case '2':
		    case '3':
		    case '4':
			// Don't let somebody resize us if we're not normally
			// resizable.
			if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW) &&
				(p->dwStyle & WS_THICKFRAME))
			    MCIWndSetZoom(hwnd, 100 * (wParam - '0'));
			return 0;	// break will ding
		    case 'P':
			MCIWndPlay(hwnd); return 0;
		    case 'S':
			MCIWndStop(hwnd); return 0;
		    case 'D':
			// The key accelerator should only work if we gave
			// them a menu for this command
			if (!(p->dwStyle & MCIWNDF_NOMENU))
			    PostMessage(hwnd, WM_COMMAND, IDM_MCICONFIG, 0);
			return 0;
		    case 'C':
			PostMessage(hwnd, WM_COMMAND, IDM_COPY, 0); return 0;
		    case VK_F5:
			// The key accelerator should only work if we gave
			// them a menu for this command
			if (!(p->dwStyle & MCIWNDF_NOMENU))
			    PostMessage(hwnd, WM_COMMAND, IDM_MCICOMMAND, 0);
			return 0;
		    case 'F':
		    case 'O':
			if (!(p->dwStyle & MCIWNDF_NOOPEN))
			    MCIWndOpenDialog(hwnd);
			return 0;
		    case 'M':
			PostMessage(hwnd, WM_COMMAND,
				GET_WM_COMMAND_MPS(ID_TOOLBAR, IDM_MENU,
					TBN_BEGINDRAG));
			return 0;
		    default:
			break;
		}
	    }
	    break;

	case WM_SYSCHAR:
	    switch(wParam) {
	        case '1':
	        case '2':
	        case '3':
	        case '4':
		    // Don't let somebody resize us if we're not normally
		    // resizable.
		    if (!(p->dwStyle & MCIWNDF_NOAUTOSIZEWINDOW) &&
				(p->dwStyle & WS_THICKFRAME))
			MCIWndSetZoom(hwnd, 100 / ((UINT) wParam - '0'));
		    return 0;	// break will ding
	        default:
		    break;
	    }
	    break;

	case WM_HSCROLL:

#define FORWARD	 1
#define BACKWARD 2

            dwPos = (DWORD) SendMessage(p->hwndTrackbar, TBM_GETPOS, 0, 0);

	    // nothing to do - spurious END without BEGIN
	    if (!p->fScrolling && wParam == TB_ENDTRACK)
		break;

	    // Turn seek exactly off while scrolling and remember what it was
	    // Also, remember if we were playing just before we seeked so we
	    // can continue playing after the seek (so moving the thumb doesn't
	    // stop the play).
            if (!p->fScrolling) {
                p->fScrolling = TRUE;
		// Wierd artifacts happen if you turn seek exactly off while
		// seeking. You see the key frame and then the actual frame.
		// Nobody can remember why this was ever a good idea.
		//p->fSeekExact = MCIWndSeekExact(p, FALSE);
		// if we're still seeking from last time, don't change this
		if (p->dwMode != MCI_MODE_SEEK)
                    p->fPlayAfterSeek = (p->dwMode == MCI_MODE_PLAY);
		// Now which direction was it playing in?
		if (p->fPlayAfterSeek) {
                    MCIWndGet(p, szStatusForward, ach, NUMELMS(ach));
                    if (ach[0] == TEXT('F') || ach[0] == TEXT('f'))
			p->fPlayAfterSeek = BACKWARD;
		    else	// by default, choose forward. Some devices
				// don't understand this command and fail.
			p->fPlayAfterSeek = FORWARD;
		}
            }

	    switch(wParam)
	    {
		case TB_LINEUP:
		    dwPos--; break;
		case TB_LINEDOWN:
		    dwPos++; break;
		case TB_PAGEUP:
		    if (p->fHasTracks) {
			dwPos = MCIWndiPrevTrack(p); break;
		    } else {
                        dwPos -= p->dwMediaLen / 16; break;
		    }
		case TB_PAGEDOWN:
		    if (p->fHasTracks) {
			dwPos = MCIWndiNextTrack(p); break;
		    } else {
                        dwPos += p->dwMediaLen / 16; break;
		    }
		case TB_TOP:
		    dwPos = p->dwMediaStart; break;
		case TB_BOTTOM:
		    dwPos = p->dwMediaStart + p->dwMediaLen; break;
		case TB_THUMBTRACK:
		case TB_THUMBPOSITION:
		    break;
		case TB_ENDTRACK:
		    // All done.  Put seek exact back to what it used to be
		    p->fScrolling = FALSE;
		    // Don't do this anymore (see above)
		    //MCIWndSeekExact(p, p->fSeekExact);
		    break;

		default:
		    break;

	    }

	    // If we're windowed, update the position as we scroll.  That would
	    // be annoying for CD or wave, though.  Also, update as soon as we
	    // let go of the thumb.  Also, never seek around while we're open
	    // or not ready.
	    if ((p->fCanWindow || !p->fScrolling) && p->dwMode != MCI_MODE_OPEN
					&& p->dwMode != MCI_MODE_NOT_READY) {
	        MCIWndSeek(hwnd, dwPos);
		MCIWndiTimerStuff(p);	// kick ourselves to update mode
	    }

	    // After we're done, if we were playing before, go back to playing
	    if (!p->fScrolling && p->fPlayAfterSeek) {
		if (p->fPlayAfterSeek == FORWARD)
                    MCIWndPlay(hwnd);
		else
                    MCIWndPlayReverse(hwnd);
		MCIWndiTimerStuff(p);	// kick ourselves to update mode
	    }

	    // Set the trackbar to the (possibly) new position
	    SendMessage(p->hwndTrackbar, TBM_SETPOS, TRUE, dwPos);
            break;

        case WM_MENUSELECT:
            break;

	// Sent from a toolbar button being pressed
        case WM_COMMAND:
            return MCIWndCommands (p, hwnd, wParam, lParam);
            break;

// When somebody presses a toolbar button in 32 bit Chicago-land, we are told
// about it via a WM_NOTIFY.  Else, we are sent a WM_COMMAND, handled elsewhere.
#ifdef _WIN32
       case WM_NOTIFY: {

#define lpHdr ((TBNOTIFY *)lParam)

           if (lpHdr->hdr.code == TBN_BEGINDRAG) {

               RECT rc;
               RECT rcT;
               MSG  msgT;


	   // We're only interested in a pressing of the Menu button
               if (lpHdr->hdr.idFrom != ID_TOOLBAR ||
	       lpHdr->iItem != IDM_MENU ||
               !SendMessage(p->hwndToolbar, TB_ISBUTTONENABLED,
                       IDM_MENU, 0) ||
               !p->hmenu)
           break;

           SendMessage(p->hwndToolbar, TB_GETITEMRECT,
               (int)SendMessage(p->hwndToolbar, TB_COMMANDTOINDEX,
                       IDM_MENU, 0),
               (LPARAM)(LPVOID)&rc);
           rcT = rc;
           ClientToScreen(p->hwndToolbar, (LPPOINT)&rc);
           ClientToScreen(p->hwndToolbar, (LPPOINT)&rc + 1);

           // Push the button down (accelerator won't have done this)
           SendMessage(p->hwndToolbar, TB_PRESSBUTTON, IDM_MENU,
               TRUE);

           // Don't allow error dlgs to come up while we're tracking.
           // That would cause windows to shatter and send shrapnel
           // flying.
           p->fTracking = TRUE;
           TrackPopupMenu(p->hmenu, 0, rc.left, rc.bottom - 1, 0,
                       hwnd, &rc);  // don't dismiss menu inside button
           p->fTracking = FALSE;

           // Bring the button back up.
           SendMessage(p->hwndToolbar, TB_PRESSBUTTON, IDM_MENU,
               FALSE);

           // What if we press the menu button to make the menu go
           // away?  It's just going to bring the menu back up again!
           // So we need to pull the click out of the queue.
           // There are bugs in the toolbar code to prevent me from
           // doing this any other way (like disabling the button)
           if (PeekMessage(&msgT, p->hwndToolbar, WM_LBUTTONDOWN,
                               WM_LBUTTONDOWN, PM_NOREMOVE)) {
               POINT pt = {MAKEPOINTS(msgT.lParam).x,
                           MAKEPOINTS(msgT.lParam).y };
               if (PtInRect(&rcT, pt))
                   PeekMessage(&msgT, p->hwndToolbar, WM_LBUTTONDOWN,
                               WM_LBUTTONDOWN, PM_REMOVE);
           }
           }
           else if (lpHdr->hdr.code == TTN_NEEDTEXT) {

                LPTOOLTIPTEXT   lpTt;
                extern HINSTANCE ghInst;	// in video\init.c

                lpTt = (LPTOOLTIPTEXT)lParam;
                LoadString( ghInst, (UINT) lpTt->hdr.idFrom,
                            lpTt->szText, NUMELMS(lpTt->szText) );
                return 0;
           }
           break;
       }
#endif

        case WM_DESTROY:
	    // !!! MMP CLOSE will be deferred till AFTER the DESTROY

	    // Don't palette kick when we're going down.
	    // 
	    p->fHasPalette = FALSE;
            MCIWndiClose(p, FALSE);  //don't leave us playing into a random DC
	    break;

	// We can't free our pointer until now, because WM_NCDESTROY is the
	// guaranteed last message we'll get.  If we do it sooner, we could
	// fault trying to use it.
	case WM_NCDESTROY:

	    if (p->hmenu) {
                DestroyMenu(p->hmenu);
		if (p->hbrDither) {
		    DeleteObject(p->hbrDither);
		    p->hbrDither = NULL;
		}
	    }

 	    if (p->pTrackStart)
		LocalFree((HANDLE)p->pTrackStart);

	    if (p->hfont) {
		// !!! Someone else may have to go and create it again, but oh
		// !!! well.
		DeleteObject(p->hfont);
		p->hfont = NULL;
	    }

	    if (p->hicon) {
		DestroyIcon(p->hicon);
		p->hicon = NULL;
	    }
	
//	    if (p->hbmToolbar) {
//		DeleteObject(p->hbmToolbar);
//		p->hbmToolbar = NULL;
//	    }

	    // We can't destroy our pointer and then fall through and use it
	    f = p->fMdiWindow;
	    LocalFree((HLOCAL) p);
            SetWindowLong(hwnd, 0, 0);       // our p
	    if (f)
		return DefMDIChildProc(hwnd, msg, wParam, lParam);
	    else
		return DefWindowProc(hwnd, msg, wParam, lParam);

	// Use a different rate for the timer depending on if we're active
	// or not.
        case WM_NCACTIVATE:
	    // MDI windows need to realize their palette here
	    if (p->wDeviceID && p->fMdiWindow && p->fHasPalette)
		MCIWndRealize(hwnd, wParam == FALSE);
#if 0
	case WM_ACTIVATE:
	    p->fActive = wParam;
	    KillTimer(hwnd, TIMER1);
	    MCIWndiSetTimer(p);
#endif
	    break;
	
	case WM_SETFOCUS:
	    p->fActive = TRUE;
	    KillTimer(hwnd, TIMER1);
	    MCIWndiSetTimer(p);
	    break;

	case WM_KILLFOCUS:
	    p->fActive = FALSE;
	    KillTimer(hwnd, TIMER1);
	    MCIWndiSetTimer(p);
	    break;

	// If the user uses MCINOTIFY we pass the notify on to the "owner"
	case MM_MCINOTIFY:
	    // Kick ourselves to update toolbar/titles since getting a notify
	    // means that stuff might have changed.
	    MCIWndiTimerStuff(p);
	    return NotifyOwner(p, msg, wParam, lParam);
	
        case WM_DRAWITEM:
        case WM_MEASUREITEM:
        case WM_DELETEITEM:
            OwnerDraw(p, msg, wParam, lParam);
            return TRUE;        // !!!

        case WM_SYSCOMMAND:
            switch (wParam & ~0xF) {
                case SC_MINIMIZE:
		    // Minimizing from MAXIMIZED state better do the same thing
		    // as restore or windows will always think it's maximized
		    // and start wierding out on us (Chico bug 19541).
		    if (IsZoomed(hwnd)) {
			wParam = SC_RESTORE | (wParam & 0xF);
			break;	// MUST let DefWndProc run
		    }
                    if (p->wDeviceID && p->fCanWindow) {
                        RECT rc;
                        MCIWndGetDest(hwnd, &rc);
                        if (rc.right  > p->rcNormal.right &&
                            rc.bottom > p->rcNormal.bottom) {

			    // We pressed buttons on the title bar... we really
			    // better autosize window.
			    DWORD dw = p->dwStyle;
			    p->dwStyle &= ~MCIWNDF_NOAUTOSIZEWINDOW;
                            MCIWndSetZoom(hwnd, 100);
			    p->dwStyle = dw;
                            return 0;
                        }
                    }
                    break;

                case SC_MAXIMIZE:
                    if (p->fCanWindow && !IsIconic(hwnd)) {
                        RECT rc;
                        MCIWndGetDest(hwnd, &rc);
                        if (rc.right  < p->rcNormal.right &&
                            rc.bottom < p->rcNormal.bottom) {

			    // We pressed buttons on the title bar... we really
			    // better autosize window.
			    DWORD dw = p->dwStyle;
			    p->dwStyle &= ~MCIWNDF_NOAUTOSIZEWINDOW;
                            MCIWndSetZoom(hwnd, 100);
			    p->dwStyle = dw;
                            return 0;
                        }
                        if (rc.right  >= p->rcNormal.right &&
                            rc.right  <  p->rcNormal.right*2 &&
                            rc.bottom >= p->rcNormal.bottom &&
                            rc.bottom <  p->rcNormal.bottom*2) {

			    // We pressed buttons on the title bar... we really
			    // better autosize window.
			    DWORD dw = p->dwStyle;
			    p->dwStyle &= ~MCIWNDF_NOAUTOSIZEWINDOW;
                            MCIWndSetZoom(hwnd, 200);
			    p->dwStyle = dw;
                            return 0;
                        }
                    }
                    break;
            }
            break;

	case WM_DROPFILES:
	    MCIWndiDrop(hwnd, wParam);
	    break;

	case WM_QUERYDRAGICON:
	    return (LONG_PTR)(UINT_PTR)p->hicon;
    }

    if (p && p->fMdiWindow)
        return DefMDIChildProc(hwnd, msg, wParam, lParam);
    else
        return DefWindowProc(hwnd, msg, wParam, lParam);
}

STATICFN void NEAR PASCAL PatRect(HDC hdc,int x,int y,int dx,int dy)
{
    RECT    rc;

    rc.left   = x;
    rc.top    = y;
    rc.right  = x + dx;
    rc.bottom = y + dy;

    ExtTextOut(hdc,0,0,ETO_OPAQUE,&rc,NULL,0,NULL);
}

#define FillRC(hdc, prc)    PatRect(hdc, (prc)->left, (prc)->top, (prc)->right - (prc)->left, (prc)->bottom-(prc)->top)

STATICFN HBITMAP NEAR PASCAL CreateDitherBitmap(void)
{
    PBITMAPINFO pbmi;
    HBITMAP hbm;
    HDC hdc;
    int i;
    long patGray[8];
    DWORD rgb;

    pbmi = (PBITMAPINFO)LocalAlloc(LPTR, sizeof(BITMAPINFOHEADER) +
		(sizeof(RGBQUAD) * 16));
    if (!pbmi)
        return NULL;

    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    pbmi->bmiHeader.biWidth = 8;
    pbmi->bmiHeader.biHeight = 8;
    pbmi->bmiHeader.biPlanes = 1;
    pbmi->bmiHeader.biBitCount = 1;
    pbmi->bmiHeader.biCompression = BI_RGB;

    rgb = GetSysColor(COLOR_BTNFACE);
    pbmi->bmiColors[0].rgbBlue  = GetBValue(rgb);
    pbmi->bmiColors[0].rgbGreen = GetGValue(rgb);
    pbmi->bmiColors[0].rgbRed   = GetRValue(rgb);
    pbmi->bmiColors[0].rgbReserved = 0;

    rgb = GetSysColor(COLOR_BTNHIGHLIGHT);
    pbmi->bmiColors[1].rgbBlue  = GetBValue(rgb);
    pbmi->bmiColors[1].rgbGreen = GetGValue(rgb);
    pbmi->bmiColors[1].rgbRed   = GetRValue(rgb);
    pbmi->bmiColors[1].rgbReserved = 0;


    /* initialize the brushes */

    for (i = 0; i < 8; i++)
       if (i & 1)
           patGray[i] = 0xAAAA5555L;   //  0x11114444L; // lighter gray
       else
           patGray[i] = 0x5555AAAAL;   //  0x11114444L; // lighter gray

    hdc = GetDC(NULL);

    hbm = CreateDIBitmap(hdc, &pbmi->bmiHeader, CBM_INIT, patGray, pbmi,
		DIB_RGB_COLORS);

    ReleaseDC(NULL, hdc);

    LocalFree((HANDLE)pbmi);

    return hbm;
}

STATICFN HBRUSH NEAR PASCAL CreateDitherBrush(void)
{
    HBITMAP hbmGray;
    HBRUSH  hbrDither;

    hbmGray = CreateDitherBitmap();
    if (hbmGray) {
	hbrDither = CreatePatternBrush(hbmGray);
	DeleteObject(hbmGray);
	return hbrDither;
    }
    return NULL;
}

//
// Draw the channel for the volume and speed menu controls
//
STATICFN void NEAR PASCAL DrawChannel(HDC hdc, LPRECT prc, HBRUSH hbrDither)
{
    HBRUSH hbrTemp;

    int iWidth = prc->right - prc->left;

    // draw the frame around the window
    SetBkColor(hdc, GetSysColor(COLOR_WINDOWFRAME));

    PatRect(hdc, prc->left, prc->top,      iWidth, 1);
    PatRect(hdc, prc->left, prc->bottom-2, iWidth, 1);
    PatRect(hdc, prc->left, prc->top,      1, prc->bottom-prc->top-1);
    PatRect(hdc, prc->right-1, prc->top, 1, prc->bottom-prc->top-1);

    SetBkColor(hdc, GetSysColor(COLOR_BTNHIGHLIGHT));
    PatRect(hdc, prc->left, prc->bottom-1, iWidth, 1);

    SetBkColor(hdc, GetSysColor(COLOR_BTNSHADOW));
    PatRect(hdc, prc->left+1, prc->top + 1, iWidth-2,1);

    // draw the background in dither gray
    hbrTemp = SelectObject(hdc, hbrDither);

    if (hbrTemp) {
        PatBlt(hdc, prc->left+1, prc->top + 2,
            iWidth-2, prc->bottom-prc->top-4, PATCOPY);
        SelectObject(hdc, hbrTemp);
    }
}

STATICFN LRESULT OwnerDraw(PMCIWND p, UINT msg, WPARAM wParam, LPARAM lParam)
{
    RECT        rc, rcMenu, rcChannel, rcThumb;
    HDC         hdc;
    int         i,dx,dy,len;
    SIZE        size;
    TCHAR       ach[10];
    HWND        hwnd = p->hwnd;

    #define lpMIS  ((LPMEASUREITEMSTRUCT)lParam)
    #define lpDIS  ((LPDRAWITEMSTRUCT)lParam)

    #define WIDTH_FROM_THIN_AIR 14
    #define CHANNEL_INDENT	6	// for VOLUME and SPEED menu trackbar
    #define MENU_WIDTH          10
    #define THUMB               5
    #define MENU_ITEM_HEIGHT	2

    switch (msg)
    {
        case WM_MEASUREITEM:

            if (p->hfont == NULL)
                p->hfont = CreateFont (8, 0, 0, 0,
		                FW_NORMAL,FALSE,FALSE,FALSE,
		                ANSI_CHARSET,OUT_DEFAULT_PRECIS,
                                CLIP_DEFAULT_PRECIS,PROOF_QUALITY,
		                VARIABLE_PITCH | FF_DONTCARE,
                                szSmallFonts);

	    //
	    // The first and last menu items are the spaces above and below
	    // the channel, so they need to be taller.
	    //
	    if (lpMIS->itemID == IDM_MCIVOLUME + VOLUME_MAX + 1
		|| lpMIS->itemID == IDM_MCISPEED + SPEED_MAX + 1
	    	|| lpMIS->itemID == IDM_MCIVOLUME + VOLUME_MAX + 2
		|| lpMIS->itemID == IDM_MCISPEED + SPEED_MAX + 2) {

                lpMIS->itemHeight = CHANNEL_INDENT;
                lpMIS->itemWidth  = MENU_WIDTH;
	    } else {
                lpMIS->itemHeight = MENU_ITEM_HEIGHT;
                lpMIS->itemWidth  = MENU_WIDTH;
	    }

	    return TRUE;

        case WM_DRAWITEM:
            rc  = lpDIS->rcItem;
            hdc = lpDIS->hDC;

	    //
	    // Something has been deselected.  If we don't see a new selection
	    // soon, it means we've dragged the cursor off the menu, and we
	    // should pop the thumb back to its original spot.
	    //
	    if ((lpDIS->itemAction & ODA_SELECT) &&
				!(lpDIS->itemState & ODS_SELECTED))
		SetTimer(p->hwnd, TIMER2, 500, NULL);
		
            //
	    // When asked to draw the selected or checked menu item, we will
	    // draw the entire menu.  Otherwise, we don't do a thing
            //
	    if (lpDIS->itemState & (ODS_SELECTED | ODS_CHECKED)) {

		// This is the item that is checked, or the original spot for
		// the thumb.  Remember it so when we drag off the menu, we
		// can bounce the thumb back here.
		if (lpDIS->itemState & ODS_CHECKED) {
		    p->uiHack = lpDIS->itemID;
	            if (p->uiHack >= IDM_MCISPEED &&
	            		p->uiHack <= IDM_MCISPEED + SPEED_MAX)
			p->hmenuHack = p->hmenuSpeed;
		    else
			p->hmenuHack = p->hmenuVolume;
		}

		// Something is being selected.  Obviously the mouse is still
		// on the menu.  Scrap our timer that was waiting to see if
		// we've dragged off the menu.
		if (lpDIS->itemState & ODS_SELECTED)
		    KillTimer(p->hwnd, TIMER2);

		// !!! Hack from Hell !!!
	        // If we try to highlight the bogus menu items, bail!
	        if (lpDIS->itemID == IDM_MCIVOLUME + VOLUME_MAX + 1)
		    break;
	        if (lpDIS->itemID == IDM_MCIVOLUME + VOLUME_MAX + 2)
		    break;
	        if (lpDIS->itemID == IDM_MCISPEED + SPEED_MAX + 1)
		    break;
	        if (lpDIS->itemID == IDM_MCISPEED + SPEED_MAX + 2)
		    break;

		// Actually set the parameter to the value we're dragging so
		// we can hear it change as we move the slider.
		// 42 means DON'T CHECK it (remember which item was originally
		// checked).
		SendMessage(hwnd, WM_COMMAND, lpDIS->itemID, 42);

		//
		// Get the rect of our menu window.  GetClipBox is
		// not quite right, so we'll adjust for the border.  Our lpDIS
		// contains the proper width of the client area, so we'll use
		// that.
		//

                GetClipBox(hdc, &rc);
                rc.top++;	//!!! top border width
                rc.bottom -= 2;	//!!! bottom border width
                rc.left = lpDIS->rcItem.left;
                rc.right = lpDIS->rcItem.right;
	 	rcMenu = rc;	// This is the rect of the whole menu

		// !!!
		// Deflate the rect to the area we want the channel to be
		// drawn in.  Use HACKY constants.
		// !!!
                i = (rc.right - rc.left - WIDTH_FROM_THIN_AIR) / 2;
                rc.top    += CHANNEL_INDENT;
                rc.bottom -= CHANNEL_INDENT;
                rc.left   += i;
                rc.right  -= i;
		rcChannel = rc;	// This is the rect of the channel

		//
		// See where the thumb belongs
		//
                rc = lpDIS->rcItem;
		rc.bottom = rc.top + 2;		// Ouch! Make sure size is 2
		
		//
		// Don't draw the thumb higher than the top of the channel
		//
		if (rc.top < rcChannel.top) {
		    rc.top = rcChannel.top;
		    rc.bottom = rc.top + 2;	// itemHeight
		}

		//
		// Don't draw the thumb below the bottom of the channel
		//
		if (rc.top > rcChannel.bottom - 2) {	// where border is
		    rc.top = rcChannel.bottom - 2;
		    rc.bottom = rc.top + 2;
		}

		//
		// Munge the rect in a bit and draw the thumb there
		//
                rc.left  += 2;
                rc.right -= 2;
                rc.bottom+= THUMB;
                rc.top   -= THUMB;

#if 0
		// Make the thumb a little bigger on the checked value
	        if (lpDIS->itemState & ODS_CHECKED) {
		    rc.top -= 1;
		    rc.bottom += 1;
		}
#endif

		rcThumb = rc;	// This is the rect of the thumb

                dx = rc.right  - rc.left;
                dy = rc.bottom - rc.top;

                SetBkColor(hdc, GetSysColor(COLOR_WINDOWFRAME));
                PatRect(hdc, rc.left+1, rc.top, dx-2,1        );
                PatRect(hdc, rc.left+1, rc.bottom-1,dx-2,1    );
                PatRect(hdc, rc.left, rc.top+1, 1,dy-2        );
                PatRect(hdc, rc.right-1,  rc.top+1, 1,dy-2    );

                InflateRect(&rc,-1,-1);
                dx = rc.right  - rc.left;
                dy = rc.bottom - rc.top;

                SetBkColor(hdc, GetSysColor(COLOR_BTNHIGHLIGHT));
                PatRect(hdc, rc.left,   rc.top,   1,dy);
                PatRect(hdc, rc.left,   rc.top,   dx,1);

                SetBkColor(hdc, GetSysColor(COLOR_BTNSHADOW));
                PatRect(hdc, rc.right-1,rc.top+1, 1,dy-1);
                PatRect(hdc, rc.left+1, rc.bottom-1, dx-1,1);

                InflateRect(&rc,-1,-1);

                SetBkColor(hdc, GetSysColor(COLOR_BTNFACE));
                SelectObject(hdc, p->hfont);
                len = wsprintf(ach, TEXT("%d"), lpMIS->itemID % 1000);

                GetTextExtentPoint(hdc, ach, len, &size);
                ExtTextOut(hdc,
                    (rc.right  + rc.left - size.cx)/2,
                    (rc.bottom + rc.top - size.cy)/2,
                    ETO_OPAQUE,&rc,ach,len,NULL);

		//
		// Exclude the ClipRect that all that garbage drew into
		//
                ExcludeClipRect(hdc, rcThumb.left, rcThumb.top,
                        rcThumb.right, rcThumb.bottom);

		//
		// Next, draw the channel
                //
                DrawChannel(hdc, &rcChannel, p->hbrDither);
		ExcludeClipRect(hdc, rcChannel.left, rcChannel.top,
                        rcChannel.right, rcChannel.bottom);

		//
		// Lastly, fill the entire menu rect with the menu colour
		//
                SetBkColor(hdc, GetSysColor(COLOR_MENU));
                FillRC(hdc, &rcMenu);
            }

            return TRUE;

	case WM_DELETEITEM:
	    return TRUE;
    }
    return TRUE;
}


//
// Code to implement the MCI command dialog box
//

void PositionWindowNearParent(HWND hwnd)
{
    RECT    rc;
    RECT    rcParent;

    GetWindowRect(hwnd, &rc);
    rc.bottom -= rc.top;
    rc.right -= rc.left;
    GetWindowRect(GetParent(hwnd), &rcParent);

    if (rcParent.bottom + rc.bottom <
				GetSystemMetrics(SM_CYSCREEN)) {
	SetWindowPos(hwnd, NULL,
		     min(rc.left, GetSystemMetrics(SM_CXSCREEN) - rc.right),
		     rcParent.bottom,
		     0, 0,
		     SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
    } else if (rc.bottom < rcParent.top) {
	SetWindowPos(hwnd, NULL,
		     min(rc.left, GetSystemMetrics(SM_CXSCREEN) - rc.right),
		     rcParent.top - rc.bottom,
		     0, 0,
		     SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
    }
}

/*--------------------------------------------------------------+
| mciDialog - bring up the dialog for MCI Send Command          |
|                                                               |
+--------------------------------------------------------------*/
INT_PTR FAR PASCAL _loadds mciDlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    TCHAR   ach[255];
    TCHAR   achT[40];
    UINT    w;
    DWORD   dw;
    PMCIWND p;
    HWND    hwndP;

    switch (msg)
    {
        case WM_INITDIALOG:
	    // Remember our actually true parent
	    SetWindowLongPtr(hwnd, DWLP_USER, lParam);
	    PositionWindowNearParent(hwnd);
            return TRUE;

        case WM_COMMAND:
            switch (GET_WM_COMMAND_ID(wParam,lParam))
            {
                case IDOK:
                    Edit_SetSel (GetDlgItem (hwnd, IDC_MCICOMMAND), 0, -1);
                    w = GetDlgItemText(hwnd, IDC_MCICOMMAND, ach, NUMELMS(ach));

		    hwndP = (HWND)GetWindowLongPtr(hwnd, DWLP_USER);
		    p = (PMCIWND)(UINT)GetWindowLong(hwndP, 0);

		    // special case the CLOSE command to do our clean up
	    	    lstrcpyn(achT, (LPTSTR)ach, lstrlen(szClose) + 1);
		    if (lstrcmpi((LPTSTR)achT, szClose) == 0) {
			MCIWndClose(hwndP);
			break;
		    }

                    dw = MCIWndGet(p, ach, ach, NUMELMS(ach));

                    if (dw != 0)
                        mciGetErrorString(dw, ach, NUMELMS(ach));

                    SetDlgItemText(hwnd, IDC_RESULT, ach);

	    	    // kick ourselves in case mode changed from this command
	    	    MCIWndiTimerStuff(p);
                    break;

                case IDCANCEL:
                    EndDialog(hwnd, FALSE);
                    break;
            }
            break;
    }

    return FALSE;
}

STATICFN BOOL NEAR PASCAL mciDialog(HWND hwnd)
{
    DialogBoxParam(hInst, MAKEINTATOM(DLG_MCICOMMAND), hwnd,
                mciDlgProc, (LPARAM)hwnd);
    return TRUE;
}


//
// Code to implement the Copy command:
//
//
// MCIWnd tries to copy the same things to the clipboard that VfW MPlayer
// would have.
//

#define SLASH(c)     ((c) == TEXT('/') || (c) == TEXT('\\'))
/**************************************************************************

    convert a file name to a fully qualifed path name, if the file
    exists on a net drive the UNC name is returned.

***************************************************************************/

STATICFN BOOL NetParseFile(LPTSTR szFile, LPTSTR szPath)
{
    TCHAR       achDrive[4];
    TCHAR       achRemote[128];
    int         cbRemote = NUMELMS(achRemote);

    // Dynamically link to WNetGetConnection.  That way MPR.DLL will only
    // get pulled in if it is needed.
#ifdef UNICODE
    typedef DWORD (APIENTRY *LPWNETGETCONNECTION)(
        LPCWSTR lpLocalName,
        LPWSTR lpRemoteName,
        LPDWORD lpnLength
        );
    char  szWNetGetConnection[] = "WNetGetConnectionW";
#else
    typedef DWORD (APIENTRY *LPWNETGETCONNECTION)(
        LPCSTR lpLocalName,
        LPSTR lpRemoteName,
        LPDWORD lpnLength
        );
    char  szWNetGetConnection[] = "WNetGetConnectionA";
#endif

    TCHAR szMpr[] = TEXT("MPR.DLL");
    LPWNETGETCONNECTION lpFn;
    HINSTANCE hInst;


    if (szPath == NULL)
        szPath = szFile;
    else
        szPath[0] = 0;

    //
    // Fully qualify the file name
    //
#ifdef _WIN32
    {
        LPTSTR pfile;
        TCHAR  achPath[MAX_PATH];

        achPath[0] = 0;
        if (GetFullPathName(szFile, MAX_PATH, achPath, &pfile) == 0) {
             return FALSE;
	}
        lstrcpy( szPath, achPath );
    }
#else
    {
        OFSTRUCT of;

        if (OpenFile(szFile, &of, OF_PARSE) == -1)
            return FALSE;

        lstrcpy(szPath, of.szPathName);
    }
#endif

    //
    // if the file is not drive based (probably UNC)
    //
    if (szPath[1] != TEXT(':'))
        return TRUE;

    achDrive[0] = szPath[0];
    achDrive[1] = TEXT(':');
    achDrive[2] = TEXT('\0');

    hInst = LoadLibrary(szMpr);
    if (hInst == NULL) {
        return FALSE;
    }
    *(FARPROC *)&lpFn = GetProcAddress( hInst, szWNetGetConnection);

    if ( (*lpFn)(achDrive, achRemote, &cbRemote) != WN_SUCCESS) {
        FreeLibrary(hInst);
        return FALSE;
    }

    FreeLibrary(hInst);

    if (!SLASH(achRemote[0]) || !SLASH(achRemote[1]))
	return TRUE;

    lstrcat(achRemote, szPath+2);
    lstrcpy(szPath, achRemote);

    return TRUE;
}



SZCODE aszMPlayerName[]           = TEXT("MPlayer");
HANDLE GetMPlayerData(PMCIWND p)
{
    TCHAR       szFileName[MAX_PATH];
    TCHAR	ach[40];
    TCHAR       szDevice[40];
    HANDLE      h;
    LPSTR       psz;
    int         len;
    LPTSTR      lpszCaption = szFileName;
    UINT	wOptions;
    RECT	rc;
    BOOL	fCompound, fFile;
    DWORD	dw;
    MCI_GETDEVCAPS_PARMS    mciDevCaps; /* for the MCI_GETDEVCAPS command */

    //
    // Get the Device Name
    //
    MCIWndGet(p, TEXT("sysinfo installname"), szDevice, NUMELMS(szDevice));

    //
    // determine if the device is simple or compound
    //
    mciDevCaps.dwItem = MCI_GETDEVCAPS_COMPOUND_DEVICE;
    dw = mciSendCommand(p->wDeviceID, MCI_GETDEVCAPS,
        MCI_GETDEVCAPS_ITEM, (DWORD_PTR)(LPSTR)&mciDevCaps);
    fCompound = (dw == 0 && mciDevCaps.dwReturn != 0);

    //
    // determine if the device handles files
    //
    if (fCompound) {
        mciDevCaps.dwItem = MCI_GETDEVCAPS_USES_FILES;
        dw = mciSendCommand(p->wDeviceID, MCI_GETDEVCAPS,
            MCI_GETDEVCAPS_ITEM, (DWORD_PTR)(LPSTR)&mciDevCaps);
        fFile = (dw == 0 && mciDevCaps.dwReturn != 0);
    }

    //
    // Compound devices that support files have an associated filename
    //
    if (fCompound && fFile) {
        lstrcpy(szFileName, p->achFileName);

        //
        // Sometimes the filename is really "device!filename" so we have to peel
        // the real filename out of it
        //
        lstrcpyn(ach, szFileName, lstrlen(szDevice) + 1);
        if ((lstrcmpi(szDevice, ach) == 0) &&
                        (szFileName[lstrlen(szDevice)] == TEXT('!'))) {
            lstrcpy(szFileName, &(p->achFileName[lstrlen(szDevice) + 1]));
        }

        NetParseFile(szFileName, (LPTSTR)NULL);
#ifndef _WIN32
        OemToAnsi(szFileName,szFileName);       // Map extended chars.
#endif
    } else {
        szFileName[0] = TEXT('\0');
    }

#ifdef DEBUG
    DPF("  GetLink: %ls|%ls!%ls\n",
        (LPTSTR)aszMPlayerName,
        (LPTSTR)szFileName,
        (LPTSTR)szDevice);
#endif

    /* How much data will we be writing? */
    len = 9 +                    // all the delimeters
          lstrlen(aszMPlayerName) +
          lstrlen(szFileName) +
          lstrlen(szDevice) +
          5 + 10 + 10 + 10 +     // max length of int and long strings
          lstrlen(lpszCaption);

    h = GlobalAlloc(GMEM_DDESHARE|GMEM_ZEROINIT, len);
    if (!h)
        return NULL;
    psz = GlobalLock(h);

    wOptions = 0x0030; // !!!! OPT_PLAY|OPT_BAR

    switch (MCIWndStatus(p, MCI_STATUS_TIME_FORMAT, 0)) {
	case MCI_FORMAT_FRAMES:
	    wOptions |= 1;	// frame mode
	    break;
	
	case MCI_FORMAT_MILLISECONDS:
	    wOptions |= 2;	// time mode
	    break;
    }
	
    MCIWndRect(p, &rc, FALSE);

    wsprintfA(psz,
#ifdef UNICODE
              "%ls%c%ls%c%ls%c%d%c%d%c%d%c%d%c%d%c%ls%c",
#else
              "%s%c%s%c%s%c%d%c%ld%c%ld%c%ld%c%d%c%s%c",
#endif
              (LPTSTR)aszMPlayerName, '\0',
              (LPTSTR)szFileName, '\0',
              (LPTSTR)szDevice, ',',
              wOptions, ',',
              0L, ',', // !!! sel start
              0L, ',', // !!! sel length
              p->dwPos, ';',
              rc.bottom - rc.top, ',',
              lpszCaption, '\0');

    return h;
}

HBITMAP FAR PASCAL BitmapMCI(PMCIWND p)
{
    HDC         hdc, hdcMem;
    HBITMAP     hbm, hbmT;
    HBRUSH      hbrOld;
    DWORD       dw;
    RECT        rc;
    HBRUSH hbrWindowColour;

    /* Minimum size of bitmap is icon size */
    int ICON_MINX = GetSystemMetrics(SM_CXICON);
    int ICON_MINY = GetSystemMetrics(SM_CYICON);

    /* Get size of a frame or an icon that we'll be drawing */
    MCIWndRect(p, &rc, FALSE);

    SetRect(&rc, 0, 0,
	    max(ICON_MINX, rc.right - rc.left),
	    max(ICON_MINX, rc.bottom - rc.top));

    hdc = GetDC(NULL);
    if (hdc == NULL)
        return NULL;
    hdcMem = CreateCompatibleDC(NULL);
    if (hdcMem == NULL) {
        ReleaseDC(NULL, hdc);
        return NULL;
    }

    /* Big enough to hold text caption too, if necessary */
    hbm = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
    ReleaseDC(NULL, hdc);
    if (hbm == NULL) {
        DeleteDC(hdcMem);
        return NULL;
    }

    hbmT = SelectObject(hdcMem, hbm);

    hbrWindowColour     = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    hbrOld = SelectObject(hdcMem, hbrWindowColour);
    PatBlt(hdcMem, 0,0, rc.right, rc.bottom, PATCOPY);
    SelectObject(hdcMem, hbrOld);
    DeleteObject(hbrWindowColour);

    if (p->wDeviceID && p->fCanWindow)
    {
	MCI_ANIM_UPDATE_PARMS mciUpdate;

	mciUpdate.hDC = hdcMem;

	dw = mciSendCommand(p->wDeviceID, MCI_UPDATE,
		    MCI_ANIM_UPDATE_HDC | MCI_WAIT,
		    (DWORD_PTR)(LPVOID)&mciUpdate);
    } else {
	DrawIcon(hdcMem, rc.left, rc.top, p->hicon);
    }

    if (hbmT)
        SelectObject(hdcMem, hbmT);
    DeleteDC(hdcMem);

    return hbm;
}

HPALETTE CopyPalette(HPALETTE hpal)
{
    PLOGPALETTE ppal;
    int         nNumEntries = 0;
    int         i;

    if (!hpal)
        return NULL;

    GetObject(hpal,sizeof(int),(LPVOID)&nNumEntries);

    if (nNumEntries == 0)
        return NULL;

    ppal = (PLOGPALETTE)LocalAlloc(LPTR,sizeof(LOGPALETTE) +
                nNumEntries * sizeof(PALETTEENTRY));

    if (!ppal)
        return NULL;

    ppal->palVersion    = 0x300;
    ppal->palNumEntries = (WORD) nNumEntries;

    GetPaletteEntries(hpal,0,nNumEntries,ppal->palPalEntry);

    for (i=0; i<nNumEntries; i++)
        ppal->palPalEntry[i].peFlags = 0;

    hpal = CreatePalette(ppal);

    LocalFree((HANDLE)ppal);
    return hpal;
}

HANDLE FAR PASCAL PictureFromDib(HANDLE hdib, HPALETTE hpal)
{
    LPMETAFILEPICT      pmfp;
    HANDLE              hmfp;
    HANDLE              hmf;
    HANDLE              hdc;
    LPBITMAPINFOHEADER  lpbi;

    if (!hdib)
        return NULL;

    lpbi = (LPVOID)GlobalLock(hdib);
    if (lpbi->biClrUsed == 0 && lpbi->biBitCount <= 8)
        lpbi->biClrUsed = 1 << lpbi->biBitCount;

    hdc = CreateMetaFile(NULL);
    if (!hdc)
        return NULL;

    SetWindowOrgEx(hdc, 0, 0, NULL);
    SetWindowExtEx(hdc, (int)lpbi->biWidth, (int)lpbi->biHeight, NULL);

    if (hpal)
    {
        SelectPalette(hdc,hpal,FALSE);
        RealizePalette(hdc);
    }

    SetStretchBltMode(hdc, COLORONCOLOR);

    StretchDIBits(hdc,
        0,0,(int)lpbi->biWidth, (int)lpbi->biHeight,
        0,0,(int)lpbi->biWidth, (int)lpbi->biHeight,
        (LPBYTE)lpbi + (int)lpbi->biSize + (int)lpbi->biClrUsed * sizeof(RGBQUAD),
        (LPBITMAPINFO)lpbi,
        DIB_RGB_COLORS,
        SRCCOPY);

    if (hpal)
        SelectPalette(hdc, GetStockObject(DEFAULT_PALETTE), FALSE);

    hmf = CloseMetaFile(hdc);

    if (hmfp = GlobalAlloc(GMEM_DDESHARE|GMEM_MOVEABLE, sizeof(METAFILEPICT)))
    {
        pmfp = (LPMETAFILEPICT)GlobalLock(hmfp);

        hdc = GetDC(NULL);
#if 1
        pmfp->mm   = MM_ANISOTROPIC;
        pmfp->hMF  = hmf;
        pmfp->xExt = MulDiv((int)lpbi->biWidth ,2540,GetDeviceCaps(hdc, LOGPIXELSX));
        pmfp->yExt = MulDiv((int)lpbi->biHeight,2540,GetDeviceCaps(hdc, LOGPIXELSX));
#else
        pmfp->mm   = MM_TEXT;
        pmfp->hMF  = hmf;
        pmfp->xExt = (int)lpbi->biWidth;
        pmfp->yExt = (int)lpbi->biHeight;
#endif
        ReleaseDC(NULL, hdc);
    }
    else
    {
        DeleteMetaFile(hmf);
    }

    return hmfp;
}

#define WIDTHBYTES(i)     ((unsigned)((i+31)&(~31))/8)  /* ULONG aligned ! */

/*
 *  DibFromBitmap()
 *
 *  Will create a global memory block in DIB format that represents the DDB
 *  passed in
 *
 */
HANDLE FAR PASCAL DibFromBitmap(HBITMAP hbm, HPALETTE hpal)
{
    BITMAP               bm;
    BITMAPINFOHEADER     bi;
    BITMAPINFOHEADER FAR *lpbi;
    DWORD                dw;
    HANDLE               hdib;
    HDC                  hdc;
    HPALETTE             hpalT;

    if (!hbm)
        return NULL;

    GetObject(hbm,sizeof(bm),(LPVOID)&bm);

    bi.biSize               = sizeof(BITMAPINFOHEADER);
    bi.biWidth              = bm.bmWidth;
    bi.biHeight             = bm.bmHeight;
    bi.biPlanes             = 1;
    bi.biBitCount           = (bm.bmPlanes * bm.bmBitsPixel) > 8 ? 24 : 8;
    bi.biCompression        = BI_RGB;
    bi.biSizeImage          = (DWORD)WIDTHBYTES(bi.biWidth * bi.biBitCount) * bi.biHeight;
    bi.biXPelsPerMeter      = 0;
    bi.biYPelsPerMeter      = 0;
    bi.biClrUsed            = bi.biBitCount == 8 ? 256 : 0;
    bi.biClrImportant       = 0;

    dw  = bi.biSize + bi.biClrUsed * sizeof(RGBQUAD) + bi.biSizeImage;

    hdib = GlobalAlloc(GHND | GMEM_DDESHARE, dw);

    if (!hdib)
        return NULL;

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hdib);
    *lpbi = bi;

    hdc = CreateCompatibleDC(NULL);

    if (hpal)
    {
        hpalT = SelectPalette(hdc,hpal,FALSE);
        RealizePalette(hdc);
    }

    GetDIBits(hdc, hbm, 0, (UINT)bi.biHeight,
        (LPBYTE)lpbi + (int)lpbi->biSize + (int)lpbi->biClrUsed * sizeof(RGBQUAD),
        (LPBITMAPINFO)lpbi, DIB_RGB_COLORS);

    if (hpal)
        SelectPalette(hdc,hpalT,FALSE);

    DeleteDC(hdc);

    return hdib;
}

SZCODE aszNative[]            = TEXT("Native");
SZCODE aszOwnerLink[]         = TEXT("OwnerLink");

// Pretend to be MPlayer copying to the clipboard
STATICFN void NEAR PASCAL MCIWndCopy(PMCIWND p)
{
    UINT	cfNative;
    UINT	cfOwnerLink;
    HBITMAP	hbm;
    HPALETTE	hpal;
    HANDLE	hdib;
    HANDLE	hmfp;

    cfNative    = RegisterClipboardFormat(aszNative);
    cfOwnerLink = RegisterClipboardFormat(aszOwnerLink);

    if (p->wDeviceID) {
	OpenClipboard(p->hwnd);
	
        EmptyClipboard();

        SetClipboardData(cfNative, GetMPlayerData(p));
        SetClipboardData(cfOwnerLink, GetMPlayerData(p));

	hbm  = BitmapMCI(p);
	hpal = MCIWndGetPalette(p->hwnd);
	hpal = CopyPalette(hpal);

	if (hbm) {
	    hdib = DibFromBitmap(hbm, hpal);

	    hmfp = PictureFromDib(hdib, hpal);

	    if (hmfp)
		SetClipboardData(CF_METAFILEPICT, hmfp);

	    if (hdib)
		SetClipboardData(CF_DIB, hdib);

	    DeleteObject(hbm);
	}

	if (hpal)
	    SetClipboardData(CF_PALETTE, hpal);

        CloseClipboard();
    }
}

/*****************************************************************************
 ****************************************************************************/

#ifdef DEBUG

STATICFN void cdecl dprintf(PSTR szFormat, ...)
{
    char ach[128];
    va_list va;

    static BOOL fDebug = -1;

    if (fDebug == -1) {
        fDebug = mmGetProfileIntA(szDebug, MODNAME, FALSE);
    }

    if (!fDebug)
        return;

    lstrcpyA(ach, MODNAME ": ");
    va_start(va,szFormat);
    wvsprintfA(ach+lstrlenA(ach),szFormat, va);
    va_end(va);
    lstrcatA(ach, "\r\n");

    OutputDebugStringA(ach);
}

#endif
