//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES OR INDEMNITIES.
//
/*++
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

Copyright  1997  Microsoft Corporation.  All Rights Reserved.

Module Name:

keyengin.cpp  

Abstract:
Functions:
Notes:
--*/
/*++


Module Name:

keyengin.cpp

Abstract:

This file contains the source code for the Runtime TUX Test slection
library.

Author:

Uknown (unknown)

Notes:


--*/
#include <windows.h>

#include <tux.h>
#include <katoex.h>

#include "ErrMacro.h"
#include <tchar.h>

#include "kbddef.h"


#define WM_LAST_CHARS  (WM_USER + 1)        // get chars in last WM_CHAR msgs (keystroke, one time, may generate multiple WM_CHARs)
#define WM_CHNG_KEYBRD (WM_USER + 2)        // tell window to change keyboards.
#define WM_EMPTY_CHARS (WM_USER + 3)        // clear last chars array

#define TEST_CHARS_EVENT        0               // offset where to set hCharsEvent that is used to signal TestKeyMapChar for retriving last chars
#define TEST_EXTRA_CHAR_EVENT   4               // offset where to set hExtraCharEvent that is designed for signalling TestKeyMapChar to get the info whether extra char(s) is generated

HANDLE             g_hThread  = NULL;

#ifdef _DEBUG
static TCHAR szDebug[1024];
#endif

static const LPTSTR cszThisFile   =     TEXT("KeyEngin.cpp");

static INT g_nNumExpectChars = 0; // records the amount of expected chars that are generated by one keystroke
/* ------------------------------------------------------------------------
The struct for creating a thread.
------------------------------------------------------------------------ */
typedef struct KEY_MAP_TEST_PARAMtag
{
    CKato       *pKato;
    HINSTANCE   hInstance;
    HANDLE      hInitEvent;
    HWND        hWnd;

} KEY_MAP_TEST_PARAM, *PKEY_MAP_TEST_PARAM;


/*++

KeyMapTestWndProc:

This function is the windows proceedure for the Keyboard mapping test.
This WndProc remembers the last WM_CHAR, WM_KEYDOWN and WM_SYSKEYDOWN
received.  When the window receive one of the custom messages 
WM_LAST_CHARS or WM_LAST_KEYS it returns the last chars or keys respectively.
(One keystroke may generate multiple WM_CHARs)

Arguments:

Standard Windows Procedure Aguments

Return Value:

Standard Windows Procedure returns

Author:

Uknown (unknown) 
Notes:

--*/
LRESULT CALLBACK KeyMapTestWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
{
    HDC             hdc;
    PAINTSTRUCT     ps;
    RECT            rect;
    static TCHAR    caLastChars[MAX_NUM_GEN_CHARS]; // stores the chars generated by one keystroke
    static int      nIndexLastChars = 0;

    static CKato    *pKato    = NULL;
    static int      iMsg      = 0;
    static LPTSTR   aszMsg[]  = { TEXT("Look in Log for test results"),
        TEXT("keymap -x50 for US-Keyboard -x60 for Japanese ... -l shows all tests"),
        TEXT("Test complete") };

    switch(message) 
    {

    case WM_CREATE:
        // if we are created/destroyed several times by the same test we have to 
        // init our variables ourselves.
        iMsg = 0;
        pKato = NULL;


        /* ----------------------------------------------------------------
        The pointer to kato is passed in as the lpCreateParams member
        of the CREATESTRUCT pointed to by lParam.
        ---------------------------------------------------------------- */
        pKato = (CKato *)((LPCREATESTRUCT)lParam)->lpCreateParams;

        __try 
        {
            if(pKato)
                pKato->Log( LOG_DETAIL, TEXT("In %s @ line %d: %s Created"),
                cszThisFile, __LINE__, ((LPCREATESTRUCT)lParam)->lpszName );
        } 
        __except( (GetExceptionCode() == STATUS_ACCESS_VIOLATION) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) 
        {
            SetLastError( ERROR_INVALID_DATA );                        
            return -1;
        } // end __try-__except

        return 0;

    case WM_PAINT: 
        hdc = BeginPaint( hwnd, &ps );
        GetClientRect( hwnd, &rect );
        DrawText( hdc, aszMsg[iMsg], -1, &rect, 
            DT_CENTER );    // | DT_VCENTER );      
        if(iMsg < 2)
            DrawText( hdc, aszMsg[iMsg+1], -1, &rect, 
            DT_CENTER | DT_VCENTER );       

        EndPaint( hwnd, &ps);
        return 0;

    case WM_KEYDOWN:
        if(pKato)
            pKato->Log( LOG_DETAIL, 
            TEXT("In %s @ line %d: Test window received WM_KEYDOWN 0x%04x %d"), 
            cszThisFile, __LINE__, wParam, wParam);
        break;

    case WM_KEYUP:
        if(pKato)
            pKato->Log( LOG_DETAIL, 
            TEXT("In %s @ line %d: Test window received WM_KEYUP 0x%04x %d"), 
            cszThisFile, __LINE__, wParam, wParam);
        break;

    case WM_SYSKEYUP:
        if(pKato)
            pKato->Log( LOG_DETAIL, 
            TEXT("In %s @ line %d: Test window received WM_SYSKEYUP 0x%04x %d"), 
            cszThisFile, __LINE__, wParam, wParam);
        break;

    case WM_EMPTY_CHARS:
        // gets caLastChars ready
        nIndexLastChars = 0;
        for(int i = 0; i < MAX_NUM_GEN_CHARS; i++)
            caLastChars[i] = 0;
        return 1;

    case WM_CHAR:
        if(nIndexLastChars == g_nNumExpectChars)
        {
            if(nIndexLastChars == MAX_NUM_GEN_CHARS)
            {
                // records the extra char at the end, and loses the oldest one
                for(int i = 0; i < (nIndexLastChars - 1); i++)
                    caLastChars[i] = caLastChars[i+1];
                nIndexLastChars--;
            }
            SetEvent((HANDLE)GetWindowLong(hwnd, TEST_EXTRA_CHAR_EVENT)); // signals extra char is generated
            if(pKato)
                pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d: Test window received extra character 0x%04x \"%c\""), cszThisFile, __LINE__, (TCHAR)wParam, (TCHAR)wParam);
        }

        caLastChars[nIndexLastChars] = (TCHAR)wParam;

        if(pKato)
            pKato->Log( LOG_DETAIL, 
            TEXT("In %s @ line %d: Test window received character 0x%04x \"%c\""), 
            cszThisFile, __LINE__, caLastChars[nIndexLastChars], caLastChars[nIndexLastChars] );
        nIndexLastChars ++;
        // signals that last chars are ready
        if((INT)nIndexLastChars == g_nNumExpectChars)
            SetEvent((HANDLE)GetWindowLong(hwnd, TEST_CHARS_EVENT));

        return 0;

    case WM_LAST_CHARS:
        // returns the address of generated-char array
        return (DWORD)caLastChars;

    case WM_CHNG_KEYBRD:
        if(pKato)
            pKato->Log(LOG_DETAIL, 
            TEXT("In %s @ line %d: Test window now using keyboard 0x%08x"), 
            cszThisFile, __LINE__, lParam);
        return (LRESULT) ActivateKeyboardLayout((HKL) lParam, NULL);

    case WM_DESTROY:
        iMsg = 2;
        InvalidateRect( hwnd, NULL, TRUE );
        UpdateWindow( hwnd );

        if(pKato)
            pKato->Log( LOG_DETAIL, TEXT("In %s @ line %d: Test window received WM_DESTROY"),
            cszThisFile, __LINE__ );
        PostQuitMessage( 0 );
        return 0;

    } // end switch(message)

    return DefWindowProc(hwnd, message, wParam, lParam);

} // end LRESULT CALLBACK SelectWindProc(HWND , UINT ,WPARAM, LPARAM)


/*++

KeyMapTestThread:

This function provides the test window that use used to determine if
what characters or keys have been pressed. The Thread creates and
window and then pumps its messages until the window is closed. 

Arguments:

pKeyMapTestParams   a struct that has the parameter for creating the
window and returning the handle to the parent
thread

Return Value:

The return value of the WndProc.    

Author:

Uknown (unknown)

Notes:

--*/
DWORD KeyMapTestThread( PKEY_MAP_TEST_PARAM pKeyMapTestParams )
{

    HWND hWnd;
    WNDCLASS  wc;
    static TCHAR    szAppName[]     = TEXT("Key Map Test Window");
    static TCHAR    szClassName[]   = TEXT("KeyMapTestWindow");
    MSG msg;
    HINSTANCE       hInstance;
    HANDLE          hCharsEvent;
    HANDLE          hExtraCharEvent;

    // Set local variables
    hInstance = pKeyMapTestParams->hInstance;

    /* --------------------------------------------------------------------
    The following event is used by the window thread to signal the
    test thread when expected WM_CHAR(s) is received.
    -------------------------------------------------------------------- */
    hCharsEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
    FUNCTION_ERROR( pKeyMapTestParams->pKato, NULL == hCharsEvent, return NULL );

    /* --------------------------------------------------------------------
    The following event is used by the window thread to signal the
    test thread when extra WM_CHAR is received.
    -------------------------------------------------------------------- */
    hExtraCharEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
    FUNCTION_ERROR( pKeyMapTestParams->pKato, NULL == hExtraCharEvent, return NULL );

    /* --------------------------------------------------------------------
    All the user to set the window name app name.
    -------------------------------------------------------------------- */
    wc.style          = 0L;
    wc.lpfnWndProc    = (WNDPROC)KeyMapTestWndProc;
    wc.cbClsExtra     = 0;
    wc.cbWndExtra     = sizeof(DWORD) + sizeof(DWORD); // stores hCharsEvent and hExtraCharEvent
    wc.hInstance      = hInstance;
    wc.hIcon          = NULL;
    wc.hCursor        = NULL;    
    wc.hbrBackground  = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName   = NULL;
    wc.lpszClassName  = szClassName;

    if( !RegisterClass(&wc) )
    {
        ExitThread( GetLastError() );

    } // end if( !RegisterClass(&wc) )

    hWnd = CreateWindow(szClassName,
        szAppName,
        WS_OVERLAPPED,
        0,
        0,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL, NULL,
        hInstance,
        (LPVOID)(pKeyMapTestParams->pKato));

    pKeyMapTestParams->pKato->Log( LOG_DETAIL,
        TEXT("In %s @ line %d: hWnd == %08Xh"),
        cszThisFile, __LINE__, hWnd );

    pKeyMapTestParams->hWnd = hWnd;
    SetEvent( pKeyMapTestParams->hInitEvent );

    if( NULL == hWnd )  
        ExitThread( GetLastError() );

    SetWindowLong( hWnd, TEST_CHARS_EVENT, (long)hCharsEvent );
    SetWindowLong( hWnd, TEST_EXTRA_CHAR_EVENT, (long)hExtraCharEvent );

    ShowWindow( hWnd, SW_SHOW );

    while( GetMessage(&msg,NULL,0,0) )
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);

    } // end while( GetMessage(&msg,NULL,0,0) )

    UnregisterClass( szClassName, hInstance );
    CloseHandle( hCharsEvent );
    CloseHandle( hExtraCharEvent);

    /* --------------------------------------------------------------------
    If not zero then erro
    -------------------------------------------------------------------- */
    ExitThread( msg.wParam );
    // Return is never reached
    return msg.wParam;

}  // end LPFUNCTION_TABLE_ENTRY TuxSelectTests( LPFUNCTION_TABLE_ENTRY lpFTE )


/*++

CreateKeyMapTestWindow:

This function creates the KeyMapTestThread that creates the Key Map Test
Window that receives the key map test keys.

Arguments:

pKato   pointer to the kato object to use or NULL if so specified.

Return Value:

HWND    handle to the window created.

Author:

Uknown (unknown)

Notes:

--*/
HWND CreateKeyMapTestWindow( HINSTANCE hInstance, CKato *pKato )
{
    KEY_MAP_TEST_PARAM KeyMapTestParams;    
    DWORD              dwRtn;

    KeyMapTestParams.hInstance  = hInstance;
    KeyMapTestParams.pKato      = pKato;
    KeyMapTestParams.hWnd       = NULL;

    KeyMapTestParams.hInitEvent = CreateEvent( NULL, TRUE, FALSE, NULL ); 
    if( NULL == KeyMapTestParams.hInitEvent ) return NULL;

    __try {

        g_hThread = CreateThread( NULL, 0,
            (LPTHREAD_START_ROUTINE)KeyMapTestThread,
            (LPVOID)&KeyMapTestParams, 0,
            &dwRtn ); 
        if( NULL == g_hThread ) __leave;

        // Wait for window to be created
        dwRtn = WaitForSingleObject( KeyMapTestParams.hInitEvent, 60000 );
        if( WAIT_TIMEOUT == dwRtn ) 
        {
            SetLastError( ERROR_GEN_FAILURE );
            __leave;

        } // end if( WAIT_TIMEOUT == dwRtn ) 

        // Insure that window creation didn't fail
        if( FALSE ==  GetExitCodeThread( g_hThread, &dwRtn ) ) __leave;

        if( STILL_ACTIVE != dwRtn )
        {
            SetLastError( dwRtn );
            __leave;

        } // end if( STILL_ACTIVE != dwRtn )

    } __finally {

        CloseHandle( KeyMapTestParams.hInitEvent );    

    } // end __try-__finally    

    return KeyMapTestParams.hWnd;

} // end HWND CreateKeyMapTestWindow( CKato *pKato )


/*++

RemoveTestWindow:

This function sends a WM_CLOSE message to the test window.

Arguments:

hWnd    the test window

Return Value:

result returned by SendMessage

Author:

Uknown (unknown)

Notes:

--*/

#define THREAD_WAIT_TIME        60000

DWORD RemoveTestWindow( HWND hWnd )
{
    DWORD   dwResult; 

    dwResult = SendMessage( hWnd, WM_CLOSE, 0, 0 ); 


    if(WAIT_OBJECT_0 == WaitForSingleObject(g_hThread, THREAD_WAIT_TIME))
        CloseHandle( g_hThread );

    return dwResult;

} // end DWORD RemoveTestWindow( HWND hWnd )


bool ChangeKeyboard(HWND hWnd, HKL newHKL)
{
    SendMessage(hWnd, WM_CHNG_KEYBRD, 0, (LPARAM) newHKL);
    return true; 
}

//
// 
//TestKeyMapChar:
//  This function performs a keymap test for characters (some keystrokes may generate more than one char, or even zero).
//
//Arguments:
//  pKato           Kato object to log too
//  hWnd            Handle of the window to receive the character
//  pKeys           Pointer to a list of keys to type
//  nKeys           Number of keys to type
//  caExpects The   characters to expect
//  nNumExpectChars The num of characters to expect
// 
//Return Value:
//  TRUE if pass, else FALSE
// 
//Author:
//  Uknown      (unknown)
//  11/30/04    chetl
// 
//Notes:
//  The keyboard must already be in the correct STATE for the test to pass.
// 
//

bool TestKeyMapChar(CKato *pKato, HWND hWnd, LPBYTE pKeys, DWORD nKeys, const TCHAR caExpects[], INT nNumExpectChars)
{
    DWORD   dwRtn;
    INT     iIdx;
    HANDLE  hCharsEvent;
    HANDLE  hExtraCharEvent;
    const TCHAR   *cpChars = NULL;
    int nRetByWM = 0;
    bool bRet = true;

    if(nKeys > MAX_SEQUENCE_LENGTH)
    {
        if(pKato)
            pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d: TEST ERROR - %d keys is greater than %d maximum."),
            cszThisFile, __LINE__, nKeys, MAX_SEQUENCE_LENGTH);
        return false;
    }

    if(!hWnd)
    {
        if(pKato)
            pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d: TEST ERROR - null window handle passed."),
            cszThisFile, __LINE__);
        return false;
    }

    if(nNumExpectChars > (INT)MAX_NUM_GEN_CHARS)
    {
        if(pKato)
            pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d: TEST ERROR - %d expected generated chars greater than %d maximum."),
            cszThisFile, __LINE__, nNumExpectChars, MAX_NUM_GEN_CHARS);
        return false;
    }

    __try
    {
        hCharsEvent = (HANDLE)GetWindowLong(hWnd, TEST_CHARS_EVENT);
        hExtraCharEvent = (HANDLE)GetWindowLong(hWnd, TEST_EXTRA_CHAR_EVENT);
        g_nNumExpectChars = nNumExpectChars;

        while(nRetByWM != 1) // waits until just created window is ready and able to response
            nRetByWM = (int)SendMessage(hWnd, WM_EMPTY_CHARS, 0, 0);

        for( iIdx = 0; iIdx < (INT)nKeys; iIdx++ )
            keybd_event( pKeys[iIdx], NULL, 0, 0 ); 

        // waits until (all WM_CHARs handled) hCharsEvent signaled
        if(nNumExpectChars > 0)
        {
            dwRtn = WaitForSingleObject(hCharsEvent, 5000);
            if(WAIT_OBJECT_0 != dwRtn)
            {
                if(pKato)
                {
                    pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d: Didn't receive Character read event."), cszThisFile, __LINE__);
                    pKato->Log(LOG_FAIL, TEXT("WaitForSingleObject return code = %luL"), dwRtn);
                 }
                bRet = false;
            }
            else
                ResetEvent(hCharsEvent);
        }
        // whether any extra char is generated, and this will slow down the test
        dwRtn = WaitForSingleObject(hExtraCharEvent, 50);
        if(WAIT_OBJECT_0 == dwRtn)
        {
            if(pKato)
                pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d: generated chars more than expected"), cszThisFile, __LINE__);
            bRet = false;
            ResetEvent(hExtraCharEvent);
        }

        cpChars = (TCHAR *)SendMessage(hWnd, WM_LAST_CHARS, 0, 0);

        for( --iIdx; iIdx >= 0; iIdx-- )
            keybd_event(pKeys[iIdx], NULL, KEYEVENTF_KEYUP, 0);

        for(INT i = 0; i < nNumExpectChars; i++)
        {
            if(caExpects[i] != cpChars[i])
            {
                if(pKato)
                    pKato->Log(LOG_FAIL, TEXT("FAIL in %s @ line %d:  # %d of %d chars. Window expected 0x%04x \"%c\" but received 0x%04x \"%c\""),
                    cszThisFile, __LINE__, i+1, nNumExpectChars, caExpects[i], caExpects[i], cpChars[i], cpChars[i]);
                bRet = false;
            }
        }       

        return bRet;
    } 
    __except( (GetExceptionCode() == STATUS_ACCESS_VIOLATION) ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH ) 
    {
        SetLastError(ERROR_INVALID_DATA);
        return false;
    }
}
