/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/*****************************************************************************\
*
* Module: oacspy.cpp
*
*   Main module for the Windows debugging OAC Spy applet.
*
* Functions:
*
*   WinMain()
*   SpyWndProc()
*   SpyInit()
*   PutOptions()
*   InitMenu()
*   SpyCommand()
*
* Comments:
*
\*****************************************************************************/

#include "oacspy.h"
#include <stdlib.h>
#include <commdlg.h>

#define WM_EXECINSTANCE     (WM_USER+100)


/*
 * Macros to simplify working with menus.
 */
#define MyEnableMenuItem(hMenu, wIDEnableItem, fEnable) \
    EnableMenuItem((hMenu),(wIDEnableItem),(fEnable)?MF_ENABLED:MF_GRAYED)

#define MyCheckMenuItem(hMenu, wIDCheckItem, fCheck) \
    CheckMenuItem((hMenu),(wIDCheckItem),(fCheck)?MF_CHECKED:MF_UNCHECKED)


HINSTANCE ghInst;
HWND ghwndSpyApp;
HWND ghwndPrintf = NULL;
HANDLE ghHookThread = NULL;
HWND ghwndSpyHook = NULL;
HWND ghwndSpyingOn = NULL;              // The window we are spying on.
HFONT ghfontPrintf;
INT gnLines;
BOOL gfSpyOn = FALSE;
BOOL gfSpyAll;
BOOL gfOutputWin;
BOOL gfOutputCom1;
BOOL gfOutputFile;
HFILE gfhFile;
CHAR gszFile[MAXSTRING];
INT gcxBorder;
INT gcyBorder;
BOOL gfMsgsUser;                        // TRUE to spy on all WM_USER messages.
BOOL gfMsgsUnknown;                     // TRUE to spy on all unknown msgs.
CHAR gszAppName[] = SPYAPPNAME;
CHAR gszMenuName[] = "SPY";
WINDOWPLACEMENT gwndpl;

BOOL gfAlwaysOnTop=FALSE;
BOOL gfCursorTrack=TRUE;
OACOBJINFO grgoiObjectSel[5];
HWND ghdlgSelectWindow=NULL, ghdlgObjectSel=NULL;
UINT WM_GETOI=0;          

PRIVATE HACCEL ghaccelTbl;              // Accelerator table handle.
PRIVATE CHAR gszSpyClassName[] = SPYCLASSNAME;


PRIVATE BOOL SpyInit(HINSTANCE hInstance, INT nCmdShow);
PRIVATE VOID PutOptions(VOID);
PRIVATE VOID InitMenu(HMENU hmenu);
PRIVATE LRESULT SpyCommand(HWND hwnd, INT nCmd, INT nNotifyCode);

#define APIError(s) PrintDebug(s) // to file & terminal
//#define APIError(s) PrintDebug(s,MessageBox(NULL, s, "API test", MB_OK))
//#define APIError(s) OutputDebugString(s) // to terminal
#define PrintDebug1(s) PrintDebug(s)
#define PrintDebug2

#ifdef DEBUG
IAccessible *gpoacFocus=NULL;
IAccessible *gpoacCreate=NULL;
IAccessible *gpoacShow=NULL;
IAccessible *gpoacReorder=NULL;
IAccessible *gpoacSelect=NULL;
IAccessible *gpoacMenu=NULL;
IAccessible *gpoacSound=NULL;
IAccessible *gpoacAlert=NULL;
IAccessible *gpoacMenuPopup=NULL;
IAccessible *gpoacCapture=NULL;
IAccessible *gpoacSwitch=NULL;
IAccessible *gpoacForeground=NULL;
IAccessible *gpoacDialog=NULL;

BOOL TestFreepoac(IAccessible *poac);
#endif

/*****************************************************************************\
* WinMain
*
* Main entry point for the Spy app.
*
\*****************************************************************************/

INT WINAPI
WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    INT nCmdShow
    )
{
    MSG msg;


    if (!SpyInit(hInstance, nCmdShow))
	return FALSE;

    WM_GETOI = RegisterWindowMessage(WMSTR_GETOI);
    if (WM_GETOI == 0) MessageBox(NULL, "RegisterWindowMessage failed(2)", "", MB_OK);

    if (OleInitialize(NULL)) 
    {
	MessageBox(NULL, "CoInitialize failed", "WimMain", MB_OK);
	return FALSE;
    }

    if (!CreateHookThread())  // Should only need this for in-process spying.
	goto closespy;

    ShowWindow(ghwndSpyApp, SW_SHOWNORMAL);

    /*
     * Polling messages from event queue
     */
    while (GetMessage(&msg, NULL, 0, 0))
    {
	if (!TranslateAccelerator(ghwndSpyApp, ghaccelTbl, &msg))
	{
	    TranslateMessage(&msg);
	    DispatchMessage(&msg);
	}
    }

closespy:
    if (IsWindow(ghwndSpyApp))
    {
	if (DestroyWindow(ghwndSpyApp))
	{
	    ghwndSpyApp = NULL;
	}
    }

    if (IsWindow(ghwndPrintf))
    {
	if (DestroyWindow(ghwndPrintf))
	{
	    ghwndPrintf = NULL;
	}
    }

    OleUninitialize();

    return (INT)msg.wParam;
}



/*****************************************************************************\
* SpyInit
*
* Initializes the Spy application.
*
* Arguments:
*   HANDLE hInstance - handle to the instance of SPY.
*   INT nCmdShow - show the window?
*
* Returns:
*   TRUE if successful, FALSE otherwise.
*
\*****************************************************************************/

PRIVATE BOOL
SpyInit(
    HINSTANCE hInstance,
    INT nCmdShow
    )
{
    WNDCLASS wc;
    HWND hwndT;
    CHAR szClassName[40];
    BOOL bFoundPrevSpy = FALSE;
    INT i;
    INT j;

    ghInst = hInstance;

    /*
     * Loop through windows to find one of the spy class.
     */
    for (hwndT = GetWindow(GetDesktopWindow(), GW_CHILD); hwndT;
	hwndT = GetWindow(hwndT, GW_HWNDNEXT))
    {
	if (GetClassName(hwndT, szClassName, 40))
	{
	    if (!lstrcmpi(szClassName, gszSpyClassName))
	    {
		bFoundPrevSpy = TRUE;
		break;
	    }
	}
    }

    if (bFoundPrevSpy)
    {
	if (hwndT)
	    SendMessage(hwndT, WM_EXECINSTANCE, 0, 0);

	return FALSE;
    }

    if (!(ghaccelTbl = LoadAccelerators(ghInst, "spy")))
	return FALSE;

    ReadRegistry();

    gcxBorder = GetSystemMetrics(SM_CXBORDER);
    gcyBorder = GetSystemMetrics(SM_CYBORDER);

    //
    // Calculate the counts in the message groups.  This is best
    // done at run time to be safe.
    //
    for (i = 0; i < gcMessages; i++)
    {
	//
	// If this message belongs to a message group,
	// increment the total for that group.
	//
	for (j = 0; j < gcMsgGroups; j++)
	{
	    if (gaMsgGroup[j].flMask & gaMsgs[i].Flags)
		gaMsgGroup[j].cMsgs++;
	}
    }

    wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon          = LoadIcon(hInstance, gszAppName);
    wc.lpszMenuName   = gszMenuName;
    wc.lpszClassName  = gszSpyClassName;
    wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hInstance      = hInstance;
    wc.style          = CS_BYTEALIGNCLIENT;
    wc.lpfnWndProc    = SpyWndProc;
    wc.cbWndExtra     = 0;
    wc.cbClsExtra     = 0;

    if (!RegisterClass(&wc))
	return FALSE;

    ghwndSpyApp = CreateWindowEx(
    //WS_EX_TOPMOST  Needed only if we awlays want the spy app to stay on top.
	0
    , gszSpyClassName, gszAppName,
	WS_OVERLAPPEDWINDOW,
	0, 0, 300, 50,
	NULL, NULL, hInstance, NULL);

    if (!ghwndSpyApp)
	return FALSE;

    if (nCmdShow != SW_SHOWNORMAL)
	gwndpl.showCmd = nCmdShow;

    SetWindowPlacement(ghwndSpyApp, &gwndpl);

    return TRUE;
}



/*****************************************************************************\
* SpyWndProc
*
* Main window procedure for the spy app.
*
* Arguments:
*    HWND hwnd - handle to the spy window
*    UINT msg - message
*    WPARAM wParam - message parameter
*    LPARAM lParam - message parameter
*
* Returns:
*   The value that the window proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

LRESULT CALLBACK
SpyWndProc(
    HWND hwnd,
    UINT msg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    switch (msg)
    {
	case WM_CREATE:
// We are not logging to client area now,
// but this line is still needed because SelectWidnow dialog depends on it.
            MyCreatePrintfWin(hwnd);

            // Check-mark some of the Option menu items 
	    {
	     HMENU hMenu;
             BOOL fScreenReaderPresent = FALSE;
             // Get a handle to the Options menu. This is at position 1. 
	     hMenu = GetSubMenu (GetMenu (hwnd), MENUPOS_OPTIONS);
             // Set default always not on top 
	     gfAlwaysOnTop = FALSE;
	     CheckMenuItem ( hMenu,
		    MENU_OPTIONS_ONTOP,
		    MF_BYCOMMAND | (gfAlwaysOnTop ? MF_CHECKED : MF_UNCHECKED));
             // Set default Track with pointer 
	     gfCursorTrack = TRUE;
	     CheckMenuItem ( hMenu,
		    MENU_OPTIONS_CURSORTRACK,
		    MF_BYCOMMAND | (gfCursorTrack ? MF_CHECKED : MF_UNCHECKED));
             // Check-mark according to the system screen-reader-present setting.
             SystemParametersInfo(SPI_GETSCREENREADER, 0, &fScreenReaderPresent, 0);
	     CheckMenuItem ( hMenu,
                    MENU_OPTIONS_SCREENREADER,
                    MF_BYCOMMAND | (fScreenReaderPresent ? MF_CHECKED : MF_UNCHECKED));
	    }

              // Automatically start the ObjectSel dialog.
//            PostMessage(hwnd, WM_COMMAND, MENU_START, 0);

             {
              UINT  uRet;
              char szFileName[80];
              UINT  cchFileName = 80;
              PrintDebug("API test for GetWindowModuleFileName");
              // bad hwnd
              uRet = GetWindowModuleFileName(NULL, szFileName, cchFileName);
              if (uRet>0) APIError("NULL hwnd in GetWindowModuleFileName should have failed");
              uRet = GetWindowModuleFileName((HWND)0xff000000, szFileName, cchFileName);
              if (uRet>0) APIError("Bad hwnd in GetWindowModuleFileName should have failed");
              // bad lpfileName
              uRet = GetWindowModuleFileName(hwnd, NULL, cchFileName);
              if (uRet>0) APIError("NULL lpFileName in GetWindowModuleFileName should have failed");
              uRet = GetWindowModuleFileName(hwnd, (LPSTR)0xff000000, cchFileName);
              if (uRet>0) APIError("Bad lpFileName in GetWindowModuleFileName should have failed");
              // boundary string size
              uRet = GetWindowModuleFileName(hwnd, szFileName, 0);
              if (uRet>0) APIError("0 string size in GetWindowModuleFileName should have failed");
              uRet = GetWindowModuleFileName(hwnd, szFileName, 80);
              if (!uRet>0) APIError("Max string size in GetWindowModuleFileName failed");
              PrintDebug("End GetWindowModuleFileName tests");
           }
           {
            GUITHREADINFO info;
            BOOL bRet;
            PrintDebug("API test for GetGUIThreadInfo");
            // Normal case
            info.cbSize = sizeof(GUITHREADINFO);
            bRet = GetGUIThreadInfo(GetCurrentThreadId(), &info);
            if (!bRet) APIError("Normal GetGUIThreadInfo failed");
            bRet = GetGUIThreadInfo(0, &info);
            if (!bRet) APIError("0 ThreadId in GetGUIThreadInfo failed");
            // bad threadid
            bRet = GetGUIThreadInfo(0xff000000, &info);
            if (bRet) APIError("Bad threadid in GetGUIThreadInfo should not succeed");
            // bad pointer to info
            bRet = GetGUIThreadInfo(GetCurrentThreadId(), NULL);
            if (bRet) APIError("NULL pinfo in GetGUIThreadInfo should have failed");
            bRet = GetGUIThreadInfo(GetCurrentThreadId(), (LPGUITHREADINFO)0xff0000);
            if (bRet) APIError("Bad pinfo in GetGUIThreadInfo should have failed");
            PrintDebug("GetGUIThreadInfo tests end");
           }

	    return 0;

	case WM_TIMER:
            // On every timer tick, if cursor stays put,
            // try to get oac object at that location.
            {
                POINT pointNew = {0,0};
                static POINT pointOld = {0,0};
                static fDone = FALSE;

                GetCursorPos(&pointNew);

                // Is the mouse pointer dwelling at the same point?
                if (IsWindow(ghdlgSelectWindow) &&
                    pointNew.x == pointOld.x && pointNew.y == pointOld.y)
                {
                    // Don't do anything when point is within spy app window
                    RECT rect, rectDlg;
                    GetWindowRect(ghwndSpyApp, &rect);
                    GetWindowRect(ghdlgSelectWindow, &rectDlg);
                    if (!fDone &&
                        (pointNew.x < rect.left ||
                         pointNew.x > rect.right ||
                         pointNew.y < rect.top ||
                         pointNew.y > rect.bottom) &&
                        (pointNew.x < rectDlg.left ||
                         pointNew.x > rectDlg.right ||
                         pointNew.y < rectDlg.top ||
                         pointNew.y > rectDlg.bottom))
                    {
                        IUnknown *punk = NULL;
                        IAccessible *poac = NULL;
                        HRESULT hr;
                        OACOBJINFO oi;

                        memset((void*)&oi, 0, sizeof(oi));
#if 0
                        //
                        // No more OleGetObjectFromPoint(), this was just
                        // short term for TOM RichEdit stuff..
                        //
                        hr = OleGetObjectFromPoint(NULL, pointNew, IID_IUnknown, (void**)&punk);
                        if (!IsHResultError(hr, "WM_TIMER, first try OleGetObjectFromPoint"))
                        {
                            hr = GetObjectInfo(punk, CHILDID_SELF, &oi);
                            IsHResultError(hr, "WM_TIMER, GetObjectInfo");
                            punk->Release();
                        }
                        else
#endif
                        {
                            VARIANT varChild;

#ifdef DEBUG  // For API test ONLY. AccessibleObjectFromPoint invalid parsms.
// User action that exercises this coe:
// 1. Bring up Spy Window dialog.
// 2. Pointer over any object outside of spy window.
// *** Uncomment when bug 6253 fixed!
                            // bad proc
                            PrintDebug("Begin AccessibleObjectFromPoint tests");
                            if (SUCCEEDED(AccessibleObjectFromPoint(pointNew, NULL, &varChild))) APIError("AcccessibleObjectFromPoint should fail when poac=NULL");
                            if (SUCCEEDED(AccessibleObjectFromPoint(pointNew, (IAccessible**)0xff000000, &varChild))) APIError("AcccessibleObjectFromPoint should fail when poac bad");
                            // bad pvarChild
                            if (SUCCEEDED(AccessibleObjectFromPoint(pointNew, &poac, NULL))) {APIError("AcccessibleObjectFromPoint should fail when pvarChild=NULL"); poac->Release();}
                            if (SUCCEEDED(AccessibleObjectFromPoint(pointNew, &poac, (VARIANT *)0xff000000))) {APIError("AcccessibleObjectFromPoint should fail when pvarChild bad"); poac->Release();}
                            PrintDebug("End AccessibleObjectFromPoint tests");
#endif

                            hr = AccessibleObjectFromPoint(pointNew, &poac, &varChild);
                            if (!IsHResultError(hr, "WM_TIMER, AccessibleObjectFromPoint"))
                            {
                                hr = oacGetObjectInfo(poac, varChild.lVal, &oi);
                                IsHResultError(hr, "WM_TIMER, oacGetObjectInfo");
                                poac->Release();
                            }
                            else oi.hr = hr;
                        }

                        UpdateSelectWindowDlg(&oi);
                    }
                        
                    fDone = TRUE;
                }
                else {pointOld = pointNew; fDone = FALSE;}
            }
	    break;

	case WM_INITMENU:
	    if (GetMenu(ghwndSpyApp) == (HMENU)wParam)
		InitMenu((HMENU)wParam);

	    break;

	case WM_COMMAND:
	    return SpyCommand(hwnd, LOWORD(wParam), HIWORD(wParam));

	case WM_ACTIVATE:
	    /*
	     * Set the focus to the printf window if we are being activated.
	     */
	    if (LOWORD(wParam))
		SetFocus(ghwndPrintf);

	    break;

	case WM_SIZE:
	    /*
	     * Size the printf window to fit into the new client area size.
	     */
	    MoveWindow(ghwndPrintf, -gcxBorder, -gcyBorder,
		LOWORD(lParam) + (2 * gcxBorder),
		HIWORD(lParam) + (2 * gcyBorder), TRUE);
	    break;

	case WM_CLOSE:
            SetSpyHook(FALSE);   // Should needed only for in-process spying,

	    if (gfhFile)
		_lclose(gfhFile);

	    SendMessage(ghwndSpyHook, WM_CLOSE, 0, 0);
	    WriteRegistry();
	    WaitForSingleObject(ghHookThread, INFINITE);
	    DestroyWindow(ghwndSpyApp);
	    break;

	case WM_DESTROY:
	    PostQuitMessage(0);   /* Kill the main window */
	    ghwndSpyApp = NULL;
	    ghwndPrintf = NULL;
	    break;

	case WM_EXECINSTANCE:
	    /*
	     * another instance of spy has been started.
	     */
	    if (IsIconic(hwnd))
		ShowWindow(hwnd,SW_SHOWNORMAL);

	    SetForegroundWindow(hwnd);
	    BringWindowToTop(hwnd);

	    break;

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

    return 0;
}



/*****************************************************************************\
* InitMenu
*
* This function grays/enables and checks/unchecks the menu items
* appropriately for the given state.
*
* Arguments:
*   HMENU hmenu - The menu handle.
*
* Returns:
*   VOID
\*****************************************************************************/

PRIVATE VOID
InitMenu(
    HMENU hmenu
    )
{
// We are not logging to client area now.
#if 0
    BOOL fEnable = !IsPrintfEmpty();

    MyEnableMenuItem(hmenu, MENU_EDIT_CUT, fEnable);
    MyEnableMenuItem(hmenu, MENU_EDIT_COPY, fEnable);
    MyEnableMenuItem(hmenu, MENU_EDIT_CLEAR, fEnable);
#endif
}



/*****************************************************************************\
* SpyCommand
*
* Handles thw WM_COMMAND messages for the Spy app.
*
* Arguments:
*   HWND hwnd       - Window handle of the main app window.
*   INT nCmd        - Command value.
*   INT nNotifyCode - The notify code.
*
* Returns:
*   The value that the window proc should return, based on the processing
*   of the specific WM_COMMAND message received.
\*****************************************************************************/

PRIVATE LRESULT
SpyCommand(
    HWND hwnd,
    INT nCmd,
    INT nNotifyCode
    )
{
    HWINEVENTHOOK hEventHook = NULL;

    switch (nCmd)
    {
	case MENU_SPY_SELECTWINDOW:
//            if (SetSpyHook(TRUE))  // Needed only for in-process spying

            // Install timer to monitor cursor move.
            if (SetTimer(hwnd, 1, 100, NULL))
	    {
                SetSpyCaption();
                MyDialogBox(DID_SELECTWINDOW, (DLGPROC)SelectWindowDlgProc);
//                PostMessage(hwnd, WM_COMMAND, MENU_STOP, 0);  // Needed only for in-process spying
                KillTimer(hwnd, 1);
	    }
	    break;

//pkw
#if 0 // Don't want to deal with version number in about box
      // Also, readme should just go into doc.
	case MENU_SPY_ABOUT:
	    MyDialogBox(DID_ABOUT, (DLGPROC)AboutDlgProc);
	    break;

	case MENU_SPY_README:
            WinExec("notepad oacspy.txt", SW_SHOW);
	    break;
#endif

	case MENU_SPY_EXIT:
	    PostMessage(hwnd, WM_CLOSE, 0, 0);
	    break;

//
// We are not logging to the client area now.
// These editing/logging supports are not needed.
//
#if 0
	case MENU_EDIT_CUT:
	    if (CopyToClipboard())
		ClearPrintfWindow(ghwndPrintf);

	    break;

	case MENU_EDIT_COPY:
	    CopyToClipboard();
	    break;

	case MENU_EDIT_CLEAR:
	    ClearPrintfWindow(ghwndPrintf);
	    break;

	case MENU_OPTIONS_FONT:
	    SelectFont();
	    break;

	case MENU_OPTIONS_OUTPUT:
	    MyDialogBox(DID_OUTPUT, (DLGPROC)OutputDlgProc);
	    break;
#endif

//
// Spy used to use this for limiting the messages to spy on.
//
#if 0
	case MENU_OPTIONS_MESSAGES:
	    MyDialogBox(DID_MESSAGES, (DLGPROC)MessagesDlgProc);
	    break;
#endif

	case MENU_OPTIONS_ONTOP:
	{
	    HMENU hMenu;
	    RECT rect;

	    /* Get a handle to the menu. This is at position 1. */
	    hMenu = GetSubMenu (GetMenu (hwnd), MENUPOS_OPTIONS);

	    /* Get the current state of the item */
	    gfAlwaysOnTop = GetMenuState ( hMenu,
				MENU_OPTIONS_ONTOP, MF_BYCOMMAND) & MF_CHECKED;

	    /* Toggle the state of the item. */
	    gfAlwaysOnTop = gfAlwaysOnTop ? FALSE : TRUE;
	    CheckMenuItem ( hMenu,
		    MENU_OPTIONS_ONTOP,
		    MF_BYCOMMAND | (gfAlwaysOnTop ? MF_CHECKED : MF_UNCHECKED));
	    GetWindowRect(hwnd, &rect);
	    SetWindowPos(hwnd, gfAlwaysOnTop ? HWND_TOPMOST : HWND_NOTOPMOST,
	      rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
	      0);

	    break;
	}

	case MENU_OPTIONS_CURSORTRACK:
	{
	    HMENU hMenu;

            /* Get a handle to the menu. This is at position 1. */
	    hMenu = GetSubMenu (GetMenu (hwnd), MENUPOS_OPTIONS);

	    /* Get the current state of the item */
	    gfCursorTrack = GetMenuState ( hMenu,
				MENU_OPTIONS_CURSORTRACK, MF_BYCOMMAND) & MF_CHECKED;

	    /* Toggle the state of the item. */
	    gfCursorTrack = gfCursorTrack ? FALSE : TRUE;
	    CheckMenuItem ( hMenu,
		    MENU_OPTIONS_CURSORTRACK,
		    MF_BYCOMMAND | (gfCursorTrack ? MF_CHECKED : MF_UNCHECKED));

	    break;
	}

        case MENU_OPTIONS_SCREENREADER:
	{
	    HMENU hMenu;
            BOOL fScreenReaderPresent;

            // Get a handle to the menu. This is at position 1. 
	    hMenu = GetSubMenu (GetMenu (hwnd), MENUPOS_OPTIONS);

            // Get the current state of the item 
            fScreenReaderPresent = GetMenuState ( hMenu,
                    MENU_OPTIONS_SCREENREADER, MF_BYCOMMAND) & MF_CHECKED;

            // Toggle the state of the item.
            fScreenReaderPresent = !fScreenReaderPresent;
            if (!SystemParametersInfo(SPI_SETSCREENREADER,
                fScreenReaderPresent, NULL, SPIF_SENDWININICHANGE))
            {
                MessageBox(hwnd, "SPI_SETSCREENREADER failed", "", MB_OK);
                fScreenReaderPresent = !fScreenReaderPresent;
            }
	    CheckMenuItem ( hMenu,
                    MENU_OPTIONS_SCREENREADER,
                    MF_BYCOMMAND | (fScreenReaderPresent ? MF_CHECKED : MF_UNCHECKED));

	    break;
	}

	case MENU_START:
	case MENU_SPY_OBJECTSEL:
//            if (SetSpyHook(TRUE))  // Needed only for in-process spying

#ifdef DEBUG // For api test ONLY. SetWinEventHook invalid param test.
            PrintDebug1("Begin SetWinEventHook invalid param test.");
           {
            HWINEVENTHOOK hTest;
            // event min/max reversed
            hTest = SetWinEventHook(EVENT_MAX, EVENT_MIN, 0, ObjectSelNotifyProc, 0, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (hTest) {APIError("SetWinEventHook should not succeed when eventMax < Min");UnhookWinEvent(hTest);}
            // Bad proc
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, NULL, 0, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (hTest) {APIError("SetWinEventHook should not succeed when proc NULL"); UnhookWinEvent(hTest);}
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, (WINEVENTPROC)0xff000000, 0, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (hTest) {APIError("SetWinEventHook should not succeed when proc Bad"); UnhookWinEvent(hTest);}
            // invalid module handle should be ignorred for OUTOFCONTEXT
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, (HMODULE)0xfffff, ObjectSelNotifyProc, 0, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (!hTest) APIError("invalid module handle should be ignorred for OUTOFCONTEXT");
            else if (!UnhookWinEvent(hTest)) APIError("Unhook failed");
            // invalid thread & process ids should not crash the system
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, ObjectSelNotifyProc, 0xffff, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (!hTest) APIError("SetWinEventHook ivalid thread id test failed");
            else if (!UnhookWinEvent(hTest)) APIError("UnhookWinEvent failed 1");
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, ObjectSelNotifyProc, 0, 0xffff, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (!hTest) APIError("SetWinEventHook ivalid process id test failed");
            else if (!UnhookWinEvent(hTest)) APIError("UnhookWinEvent failed 2");
            // in context without dll
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, ObjectSelNotifyProc, 0, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_INCONTEXT);
            if (hTest) {APIError("SetWinEventHook should fail when hModule invalide"); UnhookWinEvent(hTest);}
            // unhook invalid handle
            if (UnhookWinEvent(0xffff)) MessageBox(NULL, "UnhookWinEvent should have failed!", "test 7", MB_OK);
            if (UnhookWinEvent(0)) APIError("Unhook 0 hEvent should have failed!");
            // unhooking twice
            hTest = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, ObjectSelNotifyProc, 0, 0, WINEVENT_OUTOFCONTEXT);
            if (!hTest) APIError("SetWinEventHook failed!");
            else
            {
                if (!UnhookWinEvent(hTest)) APIError("UnhookWinEvent failed 3");
                if (UnhookWinEvent(hTest)) APIError("UnhookWinEvent twice should have failed!");
            }
           }
            PrintDebug1("End SetWinEventHook invalid param test");
#endif

            hEventHook = SetWinEventHook(EVENT_MIN, EVENT_MAX, NULL, ObjectSelNotifyProc,
                0, 0, WINEVENT_SKIPOWNPROCESS | WINEVENT_OUTOFCONTEXT);
            if (hEventHook)
	    {
                SetSpyCaption();
                MyDialogBox(DID_OBJECTSEL, (DLGPROC)ObjectSelDlgProc);
//                PostMessage(hwnd, WM_COMMAND, MENU_STOP, 0); // Needed only for in-process spying.
                UnhookWinEvent(hEventHook);
                hEventHook = NULL;

#ifdef DEBUG // API test ONLY. Delayed object freeing.
                PrintDebug1("Begin API test for delayed object freeing");
                if (gpoacFocus) TestFreepoac(gpoacFocus);
                if (gpoacCreate) TestFreepoac(gpoacCreate);
                if (gpoacShow) TestFreepoac(gpoacShow);
                if (gpoacReorder) TestFreepoac(gpoacReorder);
                if (gpoacSelect) TestFreepoac(gpoacSelect);
                if (gpoacMenu) TestFreepoac(gpoacMenu);
                if (gpoacSound) TestFreepoac(gpoacSound);
                if (gpoacAlert) TestFreepoac(gpoacAlert);
                if (gpoacMenuPopup) TestFreepoac(gpoacMenuPopup);
                if (gpoacCapture) TestFreepoac(gpoacCapture);
                if (gpoacSwitch) TestFreepoac(gpoacSwitch);
                if (gpoacForeground) TestFreepoac(gpoacForeground);
                if (gpoacDialog) TestFreepoac(gpoacDialog);
                gpoacFocus=NULL;
                gpoacCreate=NULL;
                gpoacShow=NULL;
                gpoacReorder=NULL;
                gpoacSelect=NULL;
                gpoacMenu=NULL;
                gpoacSound=NULL;
                gpoacAlert=NULL;
                gpoacMenuPopup=NULL;
                gpoacCapture=NULL;
                gpoacSwitch=NULL;
                gpoacForeground=NULL;
                gpoacDialog=NULL;
               PrintDebug1("End of test for delayed object freeing");
#endif

	    }
            else MessageBox(hwnd, "SetWinEventHook failed", "", MB_OK);

	    break;

#if 0 // Needed only for in-process spying.
	case MENU_STOP:
	    if (SetSpyHook(FALSE))
	    {
			/*
			hmenu = GetMenu(hwnd);
			ModifyMenu(hmenu, MENUPOS_STARTSTOP, MF_BYPOSITION | MF_STRING,
			    MENU_START, "S&tart!");
			DrawMenuBar(hwnd);
			*/

			SetSpyCaption();
	    }
#endif

	    break;
    }

    return 0;
}

void GetShowObjectSelInfo(HWND hwndMsg, LONG idObject, LONG indexElement, int iObjectSelTypeIndex)
{
    HRESULT hr=S_OK;
    OACOBJINFO oi;
#ifdef DEBUG
    IAccessible* pacc;
#endif

    // GetObjectInfo via IUnknown.
    IUnknown * punk=NULL;
    memset((void*)&oi, 0, sizeof(oi));

#ifdef DEBUG // API test ONLY. AccessibleObjectFromWindow invalid params
    // User action to exercise this code:
    // 1. Just open the Spy Notificaiton dialog.
    PrintDebug1("Begin AccessibleObjectFromWindow invalid params test");
    if (idObject==OBJID_CURSOR)
    {
        hr = AccessibleObjectFromWindow(NULL, idObject, IID_IUnknown, (void **)&punk);
        if (SUCCEEDED(hr)) punk->Release();
        else APIError("Failed to get cursor object using AccessibleObjectFromWindow");
    }
    else
    {
        // NUL hwnd
        hr = AccessibleObjectFromWindow(NULL, idObject, IID_IUnknown, (void **)&punk);
        if (SUCCEEDED(hr)) {APIError("NULL hwnd in AccessibleObjectFromWindow should not succeed"); punk->Release();}
    }
    // bad hwnd
    hr = AccessibleObjectFromWindow((HWND)0xbfff, idObject, IID_IUnknown, (void **)&punk);
    if (SUCCEEDED(hr)) {APIError("Bad hwnd in AccessibleObjectFromWindow should not succeed"); punk->Release();}
    // bad objid
    hr = AccessibleObjectFromWindow(hwndMsg, (DWORD)-999, IID_IUnknown, (void **)&punk);
    if (SUCCEEDED(hr)) {APIError("bad objid in AccessibleObjectFromWindow should not succeed"); punk->Release();}
    // bad refiid
    hr = AccessibleObjectFromWindow(hwndMsg, idObject, IID_ITypeComp, (void **)&punk);
    if (SUCCEEDED(hr)) {APIError("bad refiid in AccessibleObjectFromWindow should not succeed"); punk->Release();}
    // Bad punk
    hr = AccessibleObjectFromWindow(hwndMsg, idObject, IID_IUnknown, (void **)NULL);
    if (SUCCEEDED(hr)) APIError("NULL pObject in AccessibleObjectFromWindow should not succeed");
    PrintDebug1("End AccessibleObjectFromWindow invalid param test");
#endif
#ifdef DEBUG // API test ONLY.  WindowFromAccessibleObject invalid params
    // User action to exercise this code:
    // Open the Spy Notificaiton dialog. Bring up Start menu.
    PrintDebug1("Begin WindowFromAccessibleObject invalid params test");
    hr = AccessibleObjectFromWindow(hwndMsg, idObject, IID_IAccessible, (void **)&pacc);
    if (FAILED(hr)) APIError("AccessibleObjectFromWindow failed");
    else
    {
        HWND hwndResult;
        // normal
        hr = WindowFromAccessibleObject(pacc, &hwndResult);
        if (FAILED(hr)) APIError("WindowFromAccessibleObject failed");
        else if (hwndMsg != hwndResult) APIError("WindowFromAccessibleObject hwndResult != hwnd");
        // NULL pobject
        hr = WindowFromAccessibleObject(NULL, &hwndResult);
       if (SUCCEEDED(hr)) APIError("NULL pobject in WindowFromAccessibleObject wrongly succeeded");
       // bad pobject
// Uncomment when bug#10342 is fixed!!!
//       hr = WindowFromAccessibleObject((IAccessible*)0x1fff1fff, &hwndResult);
//       if (SUCCEEDED(hr)) APIError("Bad pobject in WindowFromAccessibleObject wrongly succeeded");
       // null phwnd
       hr = WindowFromAccessibleObject(pacc, NULL);
       if (SUCCEEDED(hr)) APIError("NULL phwnd in WindowFromAccessibleObject wrongly succeeded");
       // bad phwnd
       hr = WindowFromAccessibleObject(pacc, (HWND*)0x1fff1fff);
       if (SUCCEEDED(hr)) APIError("Bad phwnd in WindowFromAccessibleObject wrongly succeeded");
       pacc->Release();
    }
    PrintDebug1("End WindowFromAccessibleObject invalid param test");
#endif

    hr = AccessibleObjectFromWindow(hwndMsg, idObject, IID_IUnknown, (void **)&punk);
    if (!IsHResultError(hr, "GetShowObjectSelInfo, AccessibleObjectFromWindow") && punk)
    {
        hr = GetObjectInfo(punk, indexElement, &oi);
        IsHResultError(hr, "GetShowObjectSelInfo, GetObjectInfo");

#if 0
    // Do it directly via IAccessible.
    IAccessible * poac = NULL;
    hr = AccessibleObjectFromWindow(hwnd, OBJID_TITLEBAR, IID_IAccessible, (void **)&poac);
    if (!IsHResultError(hr, "GetShowObjectSelInfo, AccessibleObjectFromWindow") && poac)
    {
        hr = oacGetObjectInfo(poac, &oi);
        IsHResultError(hr, "oacGetObjectInfo");
#endif
    }
    else oi.hr = hr;

    UpdateObjectSelDlg(iObjectSelTypeIndex, &oi);
    if (punk) punk->Release();
}

// --------------------------------------------------------------------------
//
//  ObjectSelNotifyProc()
//
// --------------------------------------------------------------------------
void CALLBACK
ObjectSelNotifyProc
(
    HWINEVENTHOOK  hEvent,
    DWORD   event,
    HWND    hwndMsg,
    LONG    idObject,
    LONG    idChild,
    DWORD   idThread,
    DWORD   dwmsEventTime
)
{
    switch (event)
    {
        case EVENT_OBJECT_SELECTION:
        case EVENT_OBJECT_SELECTIONADD:
        case EVENT_OBJECT_SELECTIONWITHIN:
            PrintDebug("Got EVENT_OBJECT_SELECTION*: event=%lx", event);
            GetShowObjectSelInfo(hwndMsg, idObject, idChild, INDEX_SELECTION);
            break;

        case EVENT_OBJECT_FOCUS:
            PrintDebug("Got EVENT_OBJECT_FOCUS");
            GetShowObjectSelInfo(hwndMsg, idObject, idChild, INDEX_KEYBOARDFOCUS);
            break;

        case EVENT_SYSTEM_ALERT:
            if (idObject == OBJID_ALERT)
            {
                PrintDebug("Got EVENT_SYSTEM_ALERT OBJID_ALERT");
                GetShowObjectSelInfo(hwndMsg, idObject, idChild, INDEX_ALERT);
            }
            break;

        case EVENT_OBJECT_LOCATIONCHANGE:
            if (idObject == OBJID_CARET)
            {
                PrintDebug("Got EVENT_OBJECT_LOCATIONCHANGE OBJID_CARET");
                GetShowObjectSelInfo(hwndMsg, idObject, idChild, INDEX_CARET);
            }
            break;

        case EVENT_OBJECT_NAMECHANGE:
        case EVENT_OBJECT_DESCRIPTIONCHANGE:
        case EVENT_OBJECT_VALUECHANGE:
        case EVENT_OBJECT_HELPCHANGE:
            PrintDebug("Got EVENT_OBJECT_*CHANGE: event=%lx", event);
            GetShowObjectSelInfo(hwndMsg, idObject, idChild, INDEX_SEMENTICCHANGE);
            break;


        case EVENT_SYSTEM_FOREGROUND:
            if (idObject == OBJID_WINDOW)
            {
                static HWND hwndOldForeground=NULL;

                // Refresh the ObjectSel data for any new foreground window.
                if (hwndOldForeground != hwndMsg)
                {
                    memset(grgoiObjectSel, 0, sizeof(grgoiObjectSel));
                    hwndOldForeground = hwndMsg;
                }

// Normally we do not need to get titlebar info upon foreground msg.
// This is just to temporarily show that OAC is indeed working.
                // Get OAC info and show in listbox.
                GetShowObjectSelInfo(hwndMsg, OBJID_TITLEBAR, 0, INDEX_SELECTION);
            }
            break;

    }

#ifdef DEBUG // API test ONLY. AccessibleObjectFromEvent invalid params
    // User action to exercise this code:
    // 1. Just open the Spy Notificaiton dialog.
    PrintDebug1("Begin AccessibleObjectFromEvent invalid param test");
   {
    HRESULT hr=0;
    IAccessible *poac;
    VARIANT var;

    VariantInit(&var);
    // Is the window valid?
    if (hwndMsg && !IsWindow(hwndMsg))
    {
        PrintDebug("Warning: invalid hwnd 0x%lx. event 0x%lx", hwndMsg, event);
        PrintDebug("event 0x%lx, hwnd 0x%lx, idObj 0x%lx, idChild 0x%lx, hr 0x%lx", 
            event, hwndMsg, idObject, idChild, hr);
        hr = AccessibleObjectFromEvent(hwndMsg, idObject, idChild, &poac, &var);
        if (SUCCEEDED(hr)) {APIError("Got bad hwnd. AccessibleObjectFromEvent should not succeed"); poac->Release();}
        goto TestFromEventDone;
    }
    // Normal case
    hr = AccessibleObjectFromEvent(hwndMsg, idObject, idChild, &poac, &var);
    if (SUCCEEDED(hr)) 
    {
        // save some objects for testing delayed object freeing
        if (event==EVENT_OBJECT_FOCUS && !gpoacFocus) gpoacFocus=poac;
        else if (event==EVENT_OBJECT_CREATE && !gpoacCreate) gpoacCreate=poac;
        else if (event==EVENT_OBJECT_SHOW && !gpoacShow) gpoacShow=poac;
        else if (event==EVENT_OBJECT_REORDER && !gpoacReorder) gpoacReorder=poac;
        else if (event==EVENT_OBJECT_SELECTION && !gpoacSelect) gpoacSelect=poac;

        else if (event==EVENT_SYSTEM_MENUSTART && !gpoacMenu) gpoacMenu=poac;
        else if (event==EVENT_SYSTEM_SOUND && !gpoacSound) gpoacSound=poac;
        else if (event==EVENT_SYSTEM_ALERT && !gpoacAlert) gpoacAlert=poac;
        else if (event==EVENT_SYSTEM_MENUPOPUPSTART && !gpoacMenuPopup) gpoacMenuPopup=poac;
        else if (event==EVENT_SYSTEM_CAPTURESTART && !gpoacCapture) gpoacCapture=poac;
        else if (event==EVENT_SYSTEM_SWITCHSTART && !gpoacSwitch) gpoacSwitch=poac;
        else if (event==EVENT_SYSTEM_FOREGROUND && !gpoacForeground) gpoacForeground=poac;
        else if (event==EVENT_SYSTEM_DIALOGSTART && !gpoacDialog) gpoacDialog=poac;

        else TestFreepoac(poac);
    }
    else
    {
       APIError("AccessibleObjectFromEvent failed 4");
       PrintDebug("event 0x%lx, hwnd 0x%lx, idObj 0x%lx, idChild 0x%lx, hr 0x%lx", 
            event, hwndMsg, idObject, idChild, (hr));
    }
    VariantClear(&var);
    // invalid hwnd
    if (!hwndMsg && idObject!=OBJID_CURSOR) APIError("Invalid NULL hwnd in event");
    hr = AccessibleObjectFromEvent((HWND)0xbfff, idObject, idChild, &poac, &var);
    if (SUCCEEDED(hr)) {APIError("Bad hwnd in AccessibleObjectFromEvent should not succeed"); poac->Release(); VariantClear(&var);}
    // invalid idObject
    hr = AccessibleObjectFromEvent(hwndMsg, 0xffff1111, idChild, &poac, &var);
    if (SUCCEEDED(hr)) {APIError("Bad idObject in AccessibleObjectFromEvent should not succeed"); poac->Release();}
    // invalid idChild
    // Should fail for non-system object as well, but since oleacc does not
    // have control over how the server implements it, only a warning is sent.
    hr = AccessibleObjectFromEvent(hwndMsg, idObject, 0xffff0000, &poac, &var);
    if (SUCCEEDED(hr))
    {
        if (idObject < 0) APIError("System object: Bad idChild in AccessibleObjectFromEvent should not succeed"); 
        else PrintDebug("Warning: Non-system object: Bad idChild in AccessibleObjectFromEvent should not succeed");
        PrintDebug("event 0x%lx, hwnd 0x%lx, idObj 0x%lx, idChild 0x%lx, hr 0x%lx", 
            event, hwndMsg, idObject, 0xffff1111, (hr));
        poac->Release();
    }
    // Bad pObject
    hr = AccessibleObjectFromEvent(hwndMsg, idObject, idChild, NULL, &var);
    if (SUCCEEDED(hr)) {APIError("NULL pObject in AccessibleObjectFromEvent should not succeed"); poac->Release();}
    hr = AccessibleObjectFromEvent(hwndMsg, idObject, idChild, (IAccessible**)0xf0000000, &var);
    if (SUCCEEDED(hr)) {APIError("Bad pObject in AccessibleObjectFromEvent should not succeed"); poac->Release();}
    /// Bad pVar
    hr = AccessibleObjectFromEvent(hwndMsg, idObject, idChild, &poac, NULL);
    if (SUCCEEDED(hr)) {APIError("NULL pVariant in AccessibleObjectFromEvent should not succeed"); poac->Release();}
    hr = AccessibleObjectFromEvent(hwndMsg, idObject, idChild, &poac, (VARIANT*)0xf0000000);
    if (SUCCEEDED(hr)) {APIError("Bad pVariant in AccessibleObjectFromEvent should not succeed"); poac->Release();}
   }
TestFromEventDone:
    PrintDebug1("End AccessibleObjectFromEvent invalid param test");
#endif

    return;
}

// TRUE is success
BOOL TestFreepoac(IAccessible *poac)
{
    BSTR bstr=0;
    VARIANT varChild;
    BOOL Result=TRUE;
    TCHAR szName[82];
    HRESULT hr;
    VariantInit(&varChild);
    varChild.vt=VT_I4;
    varChild.lVal=0;
    hr = poac->get_accName(varChild, &bstr);
    if (FAILED(hr)) 
    {
        PrintDebug1("TestFreepoac: get_accName failed");
        Result=FALSE;
    }
    if (bstr) 
    {
        WideCharToMultiByte(CP_ACP, 0, bstr, -1, szName, 80, NULL, NULL);
        PrintDebug2("TestFreepoac: got szName [%s]", szName);
        SysFreeString(bstr);
    }
    poac->Release();
    VariantClear(&varChild);
    return Result;
}
