/******************************Module*Header*******************************\
* Module Name: dispdib32.c
*
* Fakes the display of full screen videos.
*
*
* Created: 23-03-94
* Author:  Stephen Estrop [StephenE]
*
* Copyright (c) 1994 Microsoft Corporation
\**************************************************************************/
#include <windows.h>
#include "dispdib.h"
#include "drawdib.h"

/* -------------------------------------------------------------------------
** Private constants
** -------------------------------------------------------------------------
*/
#define CX_MAX_MOVIE_DEFAULT  640
#define CY_MAX_MOVIE_DEFAULT  480

/* -------------------------------------------------------------------------
** Private functions prototypes
** -------------------------------------------------------------------------
*/
LRESULT CALLBACK
FullScreenWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
    );

LRESULT CALLBACK
KeyboardHookProc(
    int nCode,
    WPARAM wParam,
    LPARAM lParam
    );

UINT
DisplayDibEnter(
    LPBITMAPINFOHEADER lpbi,
    UINT wFlags
    );

void
DisplayDibLeave(
    UINT wFlags
    );

int
DisplayCalcMovieMultiplier(
    int cxOriginal,
    int cyOriginal,
    DWORD dwCompression
    );


/* -------------------------------------------------------------------------
** Debugging stuff
** -------------------------------------------------------------------------
*/
#if DBG

void
dprintf(
    LPSTR lpszFormat,
    ...
    );

int DebugLevel = 1;

#define  dpf( _x_ )                       dprintf _x_
#define dpf1( _x_ ) if (DebugLevel >= 1) {dprintf _x_ ;} else
#define dpf2( _x_ ) if (DebugLevel >= 2) {dprintf _x_ ;} else
#define dpf3( _x_ ) if (DebugLevel >= 3) {dprintf _x_ ;} else
#define dpf4( _x_ ) if (DebugLevel >= 4) {dprintf _x_ ;} else
#define dpf5( _x_ ) if (DebugLevel >= 5) {dprintf _x_ ;} else

#else

#define  dpf( _x_ )
#define dpf1( _x_ )
#define dpf2( _x_ )
#define dpf3( _x_ )
#define dpf4( _x_ )
#define dpf5( _x_ )

#endif



/* -------------------------------------------------------------------------
** Private Globals
** These are only valid in the process that started playing the movie.
** -------------------------------------------------------------------------
*/
HWND        hwndFullScreen;
HDC         hdcFullScreen;
HDRAWDIB    hdd;
BOOL        fClassRegistered;
int         dxScreen;
int         dyScreen;
int         iMovieSizeMultiplier;



/* -------------------------------------------------------------------------
** Global data shared between all processes that attach to this library.
** This is required to make the keyboard hook work correctly.
** -------------------------------------------------------------------------
*/
#pragma data_seg( ".sdata" )
BOOL    fStop;
HHOOK   hHookK;
#pragma data_seg()



/******************************Public*Routine******************************\
* DisplayDib
*
* Just call DisplayDibEx
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
UINT FAR PASCAL
DisplayDib(
    LPBITMAPINFOHEADER lpbi,
    LPSTR lpBits,
    UINT wFlags
    )
{
    dpf(( "Down level api, use DisplayDibEx" ));
    return DisplayDibEx( lpbi, 0, 0, lpBits, wFlags );
}




/******************************Public*Routine******************************\
* @doc EXTERNAL DISPDIB
*
* @api UINT | DisplayDibEx | This function displays a 256-color bitmap on a
*    standard VGA display. It reduces the display resolution to 320-by-200
*    or 320-by-240 and uses the full screen to display the bitmap, clipping
*    and centering it as necessary. The function normally does not return to
*    the application until the user presses a key or clicks a mouse button.
*
*    To call <f DisplayDibEx>, an application must be the active
*    application. All inactive applications and GDI screen updates
*    are suspended while <f DisplayDib> temporarily reconfigures
*    the display.
*
* @parm LPBITMAPINFO | lpbi | Specifies a pointer to a <t BITMAPINFO>
*    header describing the bitmap to be displayed.
*
* @parm int | x | x position to place DIB iff DISPLAYDIB_NOCENTER flags is set
*      the lower left is (0,0)
*
* @parm int | y | y position to place DIB iff DISPLAYDIB_NOCENTER flags is set
*      the lower left is (0,0)
*
* @parm LPSTR | lpBits | Specifies a pointer to the bitmap bits. If this
*     parameter is NULL, the bits are assumed to follow the
*     <t BITMAPINFO> structure pointed to by <p lpbi>.
*
* @parm UINT | wFlags | Specifies options for displaying the bitmap. Use
*  the following flags:
*
* @flag  DISPLAYDIB_MODE_DEFAULT | Use the default mode (320 by 240)
*    to display the bitmap.
* @flag  DISPLAYDIB_MODE_320x200x8 | Use 320-by-200 mode to display
*    the bitmap.
* @flag  DISPLAYDIB_MODE_320x240x8 | Use 320-by-240 mode to display
*    the bitmap. This is the default.
* @flag  DISPLAYDIB_NOWAIT | Return immediately after displaying the
*  bitmap; don't wait for a key press or mouse click before returning.
* @flag  DISPLAYDIB_NOPALETTE      | Ignore the palette associated
*    with the bitmap. You can use this flag when displaying a series
*    of bitmaps that use a common palette.
* @flag  DISPLAYDIB_NOCENTER       | Don't center the image. The function
*  displays the bitmap in the lower-left corner of the display.
* @flag  DISPLAYDIB_NOIMAGE        | Don't draw image
* @flag  DISPLAYDIB_ZOOM2          | Stretch image by 2
* @flag  DISPLAYDIB_DONTLOCKTASK   | dont lock out other tasks
* @flag  DISPLAYDIB_TEST           | dont do any thing just test for support
* @flag  DISPLAYDIB_BEGIN          | Switch to the low-resolution
*    display mode and set the palette. The bitmap is not displayed.
*
*    If you are displaying a series of images that use the same palette,
*    you can call <f DisplayDib> with this flag to prepare the display for
*    the bitmaps, then make a series of <f DisplayDib> calls with the
*    DISPLAYDIB_NOPALETTE flag. This technique
*    eliminates the screen flicker that occurs when the display is
*    switched between the low-resolution and standard VGA modes.
*    To return the display to standard VGA mode, subsequently
*    call <f DisplayDib> with the DISPLAYDIB_END flag.
*
* @flag  DISPLAYDIB_END            | Switch back to standard VGA mode
*    and return without displaying a bitmap. Signifies the end of multiple
*    calls to <f DisplayDib>. With this flag, you can specify
*    NULL for the <p lpbi> and <p lpBits> parameters.
*
* @rdesc Returns zero if successful, otherwise returns an error code.
*  Error codes are as follows:
*
* @flag  DISPLAYDIB_NOTSUPPORTED   | <f DisplayDib> is not supported
*   in the current mode.
* @flag  DISPLAYDIB_INVALIDDIB     | The bitmap specified by
*   <p lpbi> is not a valid bitmap.
* @flag  DISPLAYDIB_INVALIDFORMAT  | The bitmap specified by
*   <p lpbi> specifes a type of bitmap that is not supported.
* @flag  DISPLAYDIB_INVALIDTASK    | The caller is an inactive application.
*   <f DisplayDib> can only be called by an active application.
*
* @comm The <f DisplayDib> function displays bitmaps described with
*    the Windows 3.0 <t BITMAPINFO> data structure in either BI_RGB
*    or BI_RLE8 format; it does not support bitmaps described with
*    the OS/2 <t BITMAPCOREHEADER> data structure.
*
*    When <f DisplayDib> switches to a low-resolution display, it
*    disables the current display driver. As a result, you cannot use GDI
*    functions to update the display while <f DisplayDib> is displaying a
*    bitmap.
*
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
UINT FAR PASCAL
DisplayDibEx(
    LPBITMAPINFOHEADER lpbi,
    int x,
    int y,
    LPSTR lpBits,
    UINT wFlags
    )
{
    DWORD       wNumColors;
    LONG        yExt;
    LONG        xExt;
    int         xScreen,yScreen;

    /*
    ** If not already done so:
    **      Register our class and Create our window "fullscreen"
    */
    if (wFlags & DISPLAYDIB_BEGIN) {

        dpf4(( "DISPLAYDIB_BEGIN..." ));

        return DisplayDibEnter( lpbi, wFlags );
    }

    /*
    ** Just testing return OK
    */
    else if (wFlags & DISPLAYDIB_TEST) {

        dpf1(( "lpbi->biCompression = 0x%X = %c%c%c%c",
                lpbi->biCompression,
                *((LPSTR)&lpbi->biCompression + 0),
                *((LPSTR)&lpbi->biCompression + 1),
                *((LPSTR)&lpbi->biCompression + 2),
                *((LPSTR)&lpbi->biCompression + 3) ));

        dpf4(( "DISPLAYDIB_TEST... returning OK" ));
        return DISPLAYDIB_NOERROR;
    }

    /*
    ** Palette change message
    */
    else if ( (wFlags & (DISPLAYDIB_NOWAIT | DISPLAYDIB_NOIMAGE)) ==
              (DISPLAYDIB_NOWAIT | DISPLAYDIB_NOIMAGE) ) {

        PALETTEENTRY    ape[256];
        LPRGBQUAD       lprgb;
        int             i;

        lprgb = (LPRGBQUAD) ((LPBYTE) lpbi + lpbi->biSize);

        for (i = 0; i < (int) lpbi->biClrUsed; i++) {
            ape[i].peRed = lprgb[i].rgbRed;
            ape[i].peGreen = lprgb[i].rgbGreen;
            ape[i].peBlue = lprgb[i].rgbBlue;
            ape[i].peFlags = 0;
        }

        DrawDibChangePalette(hdd, 0, (int)lpbi->biClrUsed, (LPPALETTEENTRY)ape);

        return DISPLAYDIB_NOERROR;
    }

    /*
    ** Time to kill the window and the class
    */
    else if (wFlags & DISPLAYDIB_END) {

        dpf4(( "DISPLAYDIB_END..." ));
        DisplayDibLeave( wFlags );
        return DISPLAYDIB_NOERROR;
    }

    /*
    ** Do the drawing here !!
    */
    else if ( !fStop ) {

        /*
        ** If we were'nt asked to draw anything just return.
        */
        if ( wFlags & DISPLAYDIB_NOIMAGE ) {
            return DISPLAYDIB_NOERROR;
        }

        xExt = lpbi->biWidth;
        yExt = lpbi->biHeight;

        if ( wFlags & DISPLAYDIB_ZOOM2 ) {

            xExt <<= 1;
            yExt <<= 1;
        }
        else if ( iMovieSizeMultiplier ) {

            xExt <<= iMovieSizeMultiplier;
            yExt <<= iMovieSizeMultiplier;
        }

        wNumColors  = lpbi->biClrUsed;
        if (wNumColors == 0 && lpbi->biBitCount <= 8) {
            wNumColors = 1 << (UINT)lpbi->biBitCount;
        }

        /*
        ** setup pointers
        */
        if (lpBits == NULL) {
            lpBits = (LPBYTE)lpbi + lpbi->biSize + wNumColors * sizeof(RGBQUAD);
        }

        /*
        **  center the image
        */
        if (!(wFlags & DISPLAYDIB_NOCENTER)) {

            xScreen = ((int)dxScreen - xExt) / 2;
            yScreen = ((int)dyScreen - yExt) / 2;
        }
        else {

            xScreen = 0;
            yScreen = 0;
        }

        dpf5(( "Drawing to the screen..." ));
        DrawDibDraw( hdd, hdcFullScreen,
                     xScreen, yScreen, xExt, yExt,
                     lpbi, lpBits,
                     0, 0, lpbi->biWidth, lpbi->biHeight,
                     DDF_SAME_HDC | DDF_SAME_DRAW );

        return DISPLAYDIB_NOERROR;
    }

    /*
    ** The user pressed a key... time to stop
    */
    else {

        dpf4(( "The keyboard hook is telling us to stop..." ));
        DisplayDibLeave( wFlags );
        return DISPLAYDIB_NOTSUPPORTED;
    }

}



/*****************************Private*Routine******************************\
* DisplayDibEnter
*
*
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
UINT
DisplayDibEnter(
    LPBITMAPINFOHEADER lpbi,
    UINT wFlags
    )
{
    WNDCLASS    wc;
    HINSTANCE   hInst = GetModuleHandle( NULL );


    /*
    ** If our class isn't already registered with windows register it
    */
    fClassRegistered = GetClassInfo( hInst, TEXT("SJE_FULLSCREEN"), &wc );
    if ( fClassRegistered == FALSE ) {

        ZeroMemory( &wc, sizeof(wc) );

        wc.style         = CS_OWNDC;
        wc.lpfnWndProc   = FullScreenWndProc;
        wc.hInstance     = hInst;
        wc.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
        wc.lpszClassName = TEXT("SJE_FULLSCREEN");
        fClassRegistered = RegisterClass( &wc );
        dpf4(( "Class registered... %s", fClassRegistered ? "OK" : "FAILED" ));
    }


    if ( fClassRegistered ) {

        /*
        ** Do we already have a window ??
        */
        if ( hwndFullScreen == NULL ) {

            hwndFullScreen = CreateWindowEx(WS_EX_TOPMOST,
                                            TEXT("SJE_FULLSCREEN"),
                                            NULL,
                                            WS_POPUP,
                                            0, 0, 0, 0,
                                            NULL, NULL,
                                            hInst, NULL );

            dpf4(( "Window created... %s", hwndFullScreen ? "OK" : "FAILED" ));
        }

        if ( hwndFullScreen ) {

            LONG    yExt;
            LONG    xExt;
            MSG     msg;

            /*
            ** purge the queue of keyboard messages before installing
            ** the hook.
            */
            while ( PeekMessage( &msg, NULL, WM_KEYFIRST, WM_KEYLAST,
                                 PM_REMOVE | PM_NOYIELD ) );

            hHookK = SetWindowsHookEx( WH_KEYBOARD, KeyboardHookProc,
                                       GetModuleHandle(TEXT("DISPDB32.DLL")),
                                       0 );
            dpf4(( "Hook created... %s", hHookK ? "OK" : "FAILED" ));


            dxScreen = GetSystemMetrics( SM_CXSCREEN );
            dyScreen = GetSystemMetrics( SM_CYSCREEN );

            hdcFullScreen = GetDC( hwndFullScreen );
            hdd = DrawDibOpen();

            xExt = lpbi->biWidth;
            yExt = lpbi->biHeight;

            iMovieSizeMultiplier =
                DisplayCalcMovieMultiplier( xExt, yExt, lpbi->biCompression );

            if ( wFlags & DISPLAYDIB_ZOOM2 ) {

                xExt <<= 1;
                yExt <<= 1;
            }
            else if ( iMovieSizeMultiplier ) {

                xExt <<= iMovieSizeMultiplier;
                yExt <<= iMovieSizeMultiplier;
            }

            dpf1(( "Drawing at %d by %d... Flags = 0x%X", xExt, yExt, wFlags ));
            DrawDibBegin( hdd, hdcFullScreen, xExt, yExt,
                          lpbi, lpbi->biWidth, lpbi->biHeight, 0 );

            MoveWindow( hwndFullScreen, 0, 0, dxScreen, dyScreen, FALSE );
            ShowWindow( hwndFullScreen, SW_SHOW );
            UpdateWindow( hwndFullScreen );

            ShowCursor( FALSE );
            SetFocus( hwndFullScreen );
        }
    }

    fStop = FALSE;
    return hwndFullScreen != NULL ? DISPLAYDIB_NOERROR : DISPLAYDIB_NOTSUPPORTED;
}



/*****************************Private*Routine******************************\
* DisplayDibLeave
*
*
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
void
DisplayDibLeave(
    UINT wFlags
    )
{
    if (hwndFullScreen) {
        DestroyWindow( hwndFullScreen );
        hwndFullScreen = NULL;
    }

}

/*****************************Private*Routine******************************\
* DisplayCalcMovieMultiplier
*
* Determines the largest movie that the display is capable of displaying.
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
int
DisplayCalcMovieMultiplier(
    int cxOriginal,
    int cyOriginal,
    DWORD dwCompression
    )
{
    SYSTEM_INFO     SysInfo;
    int             iMult;
    int             iMultTemp;
    int             cxOriginalSave, cyOriginalSave;


    /*
    ** For now, don't try to stretch 'cvid' compressed movies.  This is
    ** because it looks bad!!
    */
    if ( *((LPSTR)&dwCompression + 0) == 'c'
      && *((LPSTR)&dwCompression + 1) == 'v'
      && *((LPSTR)&dwCompression + 2) == 'i'
      && *((LPSTR)&dwCompression + 3) == 'd' ) {

        return 0;
    }


    GetSystemInfo( &SysInfo );
    iMultTemp = iMult = 0;
    cxOriginalSave = cxOriginal;
    cyOriginalSave = cyOriginal;

    switch ( SysInfo.wProcessorArchitecture ) {

    case PROCESSOR_ARCHITECTURE_INTEL:
        if ( SysInfo.wProcessorLevel <= 3 ) {
            break;
        }

        /*
        ** maybe later we will do something different for i486's
        ** for now they just fall through to the RISC / Pentium default
        ** case below.
        */

    default:

        while ( (cxOriginal <= CX_MAX_MOVIE_DEFAULT)
             && (cyOriginal <= CY_MAX_MOVIE_DEFAULT) ) {

            iMult = iMultTemp;
            iMultTemp++;

            cxOriginal = cxOriginalSave << iMultTemp;
            cyOriginal = cyOriginalSave << iMultTemp;
        }
        break;
    }

    return iMult;
}


/******************************Public*Routine******************************\
* FullScreenWndProc
*
*
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
LRESULT CALLBACK
FullScreenWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch ( message ) {

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT        rc;

            dpf4(( "Window needs painting" ));
            BeginPaint( hwnd, &ps );
            GetUpdateRect( hwnd, &rc, FALSE );
            FillRect( hdcFullScreen, &rc, GetStockObject( BLACK_BRUSH ) );
            EndPaint( hwnd, &ps );
        }
        break;

    case WM_PALETTECHANGED:
        if ( (HWND)wParam == hwnd ) {
            break;
        }

        /* fall thru */

    case WM_QUERYNEWPALETTE:
        if ( DrawDibRealize( hdd, hdcFullScreen, FALSE ) > 0 ) {
            InvalidateRect( hwnd, NULL, TRUE );
        }
        break;

    case WM_DESTROY:
        dpf4(( "Window destroyed releasing DC" ));
        ReleaseDC( hwnd, hdcFullScreen );
        DrawDibEnd( hdd );
        DrawDibClose( hdd );

        UnregisterClass( TEXT("SJE_FULLSCREEN"), GetModuleHandle( NULL ) );

        fClassRegistered = FALSE;
        fStop = FALSE;

        ShowCursor( TRUE );
        UnhookWindowsHookEx( hHookK );
        break;

    default:
        return DefWindowProc( hwnd, message, wParam, lParam );
    }

    return (LRESULT)FALSE;
}



/******************************Public*Routine******************************\
* KeyboardHookProc
*
*
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
LRESULT CALLBACK
KeyboardHookProc(
    int nCode,
    WPARAM wParam,
    LPARAM lParam
    )
{
    if ( nCode == HC_ACTION) {

        /*
        ** Only interested in keydowns
        */
        if ( !(lParam & 0x80000000) ) {
            dpf4(( "Stop requested from the keyboard hook" ));
            fStop = TRUE;
        }
    }

    return CallNextHookEx( hHookK, nCode, wParam, lParam );
}


#if DBG
/*****************************Private*Routine******************************\
* dprintf
*
* Standard debug out stuff
*
* History:
* 23-03-94 - StephenE - Created
*
\**************************************************************************/
void
dprintf(
    LPSTR lpszFormat,
    ...
    )
{
    char buf[512];
    UINT n;
    va_list va;

    n = wsprintfA(buf, "DISPDB32: (tid %x) ", GetCurrentThreadId());

    va_start(va, lpszFormat);
    n += wvsprintfA(buf+n, lpszFormat, va);
    va_end(va);

    buf[n++] = '\n';
    buf[n] = 0;
    OutputDebugStringA(buf);
}
#endif
