The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 *  $Id: GuiTest.xs,v 1.6 2010/11/24 19:55:08 int32 Exp $
 *
 *  The SendKeys function is based on the Delphi sourcecode
 *  published by Al Williams <http://www.al-williams.com/awc/>
 *  in Dr.Dobbs <http://www.drdobbs.com/keys-to-the-kingdom/184410429>
 *
 *  Copyright (c) 1998-2002 by Ernesto Guisado <erngui@acm.org>
 *  Copyright (c) 2004 by Dennis K. Paulsen <ctrondlp@cpan.org>
 *
 *  You may distribute under the terms of either the GNU General Public
 *  License or the Artistic License.
 *
 */

#define WIN32_LEAN_AND_MEAN
#define _WIN32_IE 0x0500
#include <windows.h>
#include <commctrl.h>
#include "dibsect.h"
#include "RSNDMSG.h"


#ifdef __cplusplus
//extern "C" {
#endif
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#ifdef __cplusplus
//}
#endif

#define MAX_DATA_BUF 1024
#define NUL '\0'

#ifdef _MSC_VER
#define strncasecmp _strnicmp
#endif

HINSTANCE g_hDLL = NULL;

#if defined (__GNUC__)
	#define SHARED_ATTR __attribute__((section ("shared_seg"), shared))
#else
	#define SHARED_ATTR
#endif

#pragma data_seg(".shared")
// Used by hooking/injected routines
HWND g_hWnd SHARED_ATTR = 0;
HHOOK g_hHook SHARED_ATTR = NULL;
HWND g_popup SHARED_ATTR = 0;   //Hold's popup menu's handle
BOOL g_bRetVal SHARED_ATTR = 0;
char g_szBuffer[MAX_DATA_BUF+1] SHARED_ATTR = {NUL};
UINT WM_LV_GETTEXT SHARED_ATTR = 0;
UINT WM_LV_SELBYINDEX SHARED_ATTR = 0;
UINT WM_LV_SELBYTEXT SHARED_ATTR = 0;
UINT WM_LV_ISSEL SHARED_ATTR = 0;
UINT WM_TC_GETTEXT SHARED_ATTR = 0;
UINT WM_TC_SELBYINDEX SHARED_ATTR = 0;
UINT WM_TC_SELBYTEXT SHARED_ATTR = 0;
UINT WM_TC_ISSEL SHARED_ATTR = 0;
UINT WM_TV_SELBYPATH SHARED_ATTR = 0;
UINT WM_TV_GETSELPATH SHARED_ATTR = 0;
UINT WM_INITMENUPOPUPX SHARED_ATTR = WM_INITMENUPOPUP;  //Only needed to conform with SetHook()'s calling convention
BOOL unicode_semantics SHARED_ATTR = 0;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.shared,RWS")

extern "C" BOOL WINAPI DllMain(HANDLE hModule, DWORD  ul_reason_for_call,
                      LPVOID lpReserved)
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		// Value used by SetWindowsHookEx, etc.
		g_hDLL = (HINSTANCE)hModule;
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}

	return TRUE;
}

// Gets a treeview item handle by name
HTREEITEM GetTVItemByName(HWND hWnd, HTREEITEM hItem,
                         char *lpItemName)
{
    // If hItem is NULL, start search from root item.
    if (hItem == NULL)
        hItem = (HTREEITEM)SendMessage(hWnd, TVM_GETNEXTITEM, TVGN_ROOT, 0);

    while (hItem != NULL)
    {
        char szBuffer[MAX_DATA_BUF+1];
        TV_ITEM item;

        item.hItem = hItem;
        item.mask = TVIF_TEXT | TVIF_CHILDREN;
        item.pszText = szBuffer;
        item.cchTextMax = MAX_DATA_BUF;
        SendMessage(hWnd, TVM_GETITEM, 0, (LPARAM)&item);

        // Did we find it?
        if (lstrcmpi(szBuffer, lpItemName) == 0)
            return hItem;

        // Check whether we have child items.
        if (item.cChildren)
        {
            // Recursively traverse child items.
            HTREEITEM hItemFound, hItemChild;

            hItemChild = (HTREEITEM)SendMessage(hWnd, TVM_GETNEXTITEM,
                                                TVGN_CHILD, (LPARAM)hItem);
            hItemFound = GetTVItemByName(hWnd, hItemChild, lpItemName);

            // Did we find it?
            if (hItemFound != NULL)
                return hItemFound;
        }

        // Go to next sibling item.
        hItem = (HTREEITEM)SendMessage(hWnd, TVM_GETNEXTITEM,
                                       TVGN_NEXT, (LPARAM)hItem);
    }

    // Not found.
    return NULL;
}

int TabCtrl_GetItemText(HWND hwnd, int iItem, char *lpString, size_t sizeStr)
{
	TCITEM tcItem;
	tcItem.pszText = lpString;
	tcItem.cchTextMax = sizeStr;
	tcItem.mask = TCIF_TEXT;

	assert(lpString != NULL);
	*lpString = NUL;
	TabCtrl_GetItem(g_hWnd, iItem, &tcItem);

	return (int)strlen(lpString);
}

// Hook procedure, does most of the work for various 32bit custom control
// routines
#define pCW ((CWPSTRUCT*)lParam)
LRESULT HookProc (int code, WPARAM wParam, LPARAM lParam)
{
	//// List Views ////
	if (pCW->message == WM_LV_GETTEXT) {
		*g_szBuffer = NUL;
		int iItem = pCW->wParam;
		int iSubItem = pCW->lParam;
		ListView_GetItemText(g_hWnd, iItem, iSubItem, g_szBuffer, MAX_DATA_BUF);
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_LV_SELBYINDEX) {
		int iCount = ListView_GetItemCount(g_hWnd);
		int iSel = pCW->wParam;
		BOOL bMulti = pCW->lParam;
		// Clear out any previous selections if needed
		if (!bMulti && ListView_GetSelectedCount(g_hWnd) > 0) {
			for (int i = 0; i < iCount; i++) {
				ListView_SetItemState(g_hWnd, i, 0, LVIS_SELECTED);
			}
		}
		// Select item
		ListView_SetItemState(g_hWnd, iSel, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
		g_bRetVal = ListView_EnsureVisible(g_hWnd, iSel, FALSE);
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_LV_SELBYTEXT) {
		char szItem[MAX_DATA_BUF+1] = "";
		int iCount = ListView_GetItemCount(g_hWnd);
		BOOL bMulti = pCW->lParam;
		// Clear out any previous selections if needed
		if (!bMulti && ListView_GetSelectedCount(g_hWnd) > 0) {
			for (int i = 0; i < iCount; i++) {
				ListView_SetItemState(g_hWnd, i, 0, LVIS_SELECTED);
			}
		}
		// Look for item
		for (int i = 0; i < iCount; i++) {
			ListView_GetItemText(g_hWnd, i, 0, szItem, MAX_DATA_BUF);
			if (lstrcmpi(g_szBuffer, szItem) == 0) {
				// Found it, select it
				ListView_SetItemState(g_hWnd, i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
				g_bRetVal = ListView_EnsureVisible(g_hWnd, i, FALSE);
				break;
			}
		}
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_LV_ISSEL) {
		char szItem[MAX_DATA_BUF+1] = "";
		int iCount = ListView_GetItemCount(g_hWnd);
		g_bRetVal = FALSE; // Assume false
		// Are there any selected?
		if (ListView_GetSelectedCount(g_hWnd) > 0) {
			// Look for item
			for (int i = 0; i < iCount; i++) {
				ListView_GetItemText(g_hWnd, i, 0, szItem, MAX_DATA_BUF);
				if (lstrcmpi(g_szBuffer, szItem) == 0) {
					// Found it, determine if currently selected
					if (ListView_GetItemState(g_hWnd, i, LVIS_SELECTED) & LVIS_SELECTED) {
						g_bRetVal = TRUE;
					}
				}
			}
		}
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_TV_SELBYPATH) {
	//// Tree Views ////
		char szName[MAX_DATA_BUF+1] = "";
		size_t pos = 0, len = 0;
		HTREEITEM hItem = NULL;

		g_bRetVal = FALSE; // Assume failure

		len = strlen(g_szBuffer);
		// Move through supplied tree view path, updating hItem appropriately
		for (size_t x = 0; x < len; x++) {
			if (g_szBuffer[x] == '|') {
				if (*szName) {
					hItem = GetTVItemByName(g_hWnd, hItem, szName);
					memset(&szName, 0, MAX_DATA_BUF);
					pos = 0;
				}
			} else {
				szName[pos++] = g_szBuffer[x];
			}
		}

		if (*szName) {
			// Just a root item, no path delimiters (|)
			// OR a trailing child item?
			hItem = GetTVItemByName(g_hWnd, hItem, szName);
		}

		// Select Item if handle obtained
		g_bRetVal = hItem ? (BOOL)TreeView_SelectItem(g_hWnd, hItem) : FALSE;
		TreeView_EnsureVisible(g_hWnd, hItem);

		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_TV_GETSELPATH) {
		char szText[MAX_DATA_BUF+1] = "";
		char szTmp[MAX_DATA_BUF+1] = "";
		TVITEM tvItem = {NUL};
		HTREEITEM hItem = TreeView_GetSelection(g_hWnd);
		*g_szBuffer = NUL;

		tvItem.mask = TVIF_TEXT;
		tvItem.pszText = szText;
		tvItem.cchTextMax = MAX_DATA_BUF;
		do {
			tvItem.hItem = hItem;
			TreeView_GetItem(g_hWnd, &tvItem);

			// Add in child path text if any
			if (*szTmp)
				lstrcat(szText, szTmp);

			hItem = TreeView_GetParent(g_hWnd, hItem);
			if (hItem) {
				// Has parent, so store delimiter and path text thus far
				sprintf(szTmp, "|%s", szText);
			} else {
				// No parent, so store complete path thus far
				lstrcpy(szTmp, szText);
			}
		} while (hItem);
		lstrcpy(g_szBuffer, szTmp);
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_TC_GETTEXT) {
	//// Tab Control ////
		int iItem = pCW->wParam;
		g_bRetVal = (BOOL)TabCtrl_GetItemText(g_hWnd, iItem, g_szBuffer, MAX_DATA_BUF);
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_TC_SELBYINDEX) {
		int iItem = pCW->wParam;
		g_bRetVal = FALSE; // Assume failure
		if (iItem < TabCtrl_GetItemCount(g_hWnd)) {
			TabCtrl_SetCurFocus(g_hWnd, iItem);
			g_bRetVal = TRUE;
		}
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_TC_SELBYTEXT) {
		char szName[MAX_DATA_BUF+1] = "";
		int iCount = TabCtrl_GetItemCount(g_hWnd);
		for (int i = 0; i < iCount; i++) {
			TabCtrl_GetItemText(g_hWnd, i, szName, MAX_DATA_BUF);
			// Is Tab item we want?
			if (lstrcmpi(g_szBuffer, szName) == 0) {
				// ... then set focus to it
				TabCtrl_SetCurFocus(g_hWnd, i);
				break;
			}
		}
		UnhookWindowsHookEx(g_hHook);
	} else if (pCW->message == WM_TC_ISSEL) {
		char szName[MAX_DATA_BUF+1] = "";
		int iItem = TabCtrl_GetCurFocus(g_hWnd);
		g_bRetVal = FALSE; // Assume false
		TabCtrl_GetItemText(g_hWnd, iItem, szName, MAX_DATA_BUF);
		if (lstrcmpi(g_szBuffer, szName) == 0) {
			// Yes, selected
			g_bRetVal = TRUE;
		}
		UnhookWindowsHookEx(g_hHook);
    } else if (pCW->message == WM_INITMENUPOPUP) {
		g_popup = (HWND) pCW->wParam;
		UnhookWindowsHookEx(g_hHook);
    }

	return CallNextHookEx(g_hHook, code, wParam, lParam);
}

// Sets up the hook, global control/hook handles, and registers appropriate
// window message.
HHOOK SetHook(HWND hWnd, UINT &uMsg, const char *lpMsgId)
{
	g_hWnd = hWnd;
	if (! g_hDLL) {
		g_hDLL=GetModuleHandle("GuiTest.dll");
		fprintf(stderr,"had to get module handle: %x\n",g_hDLL);
	}

	// Give up rest of time slice, so g_hHook assignment and
	// SetWindowsHook will process.
	Sleep(0);

	// Hook the thread, that "owns" our control
	g_hHook = SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)HookProc,
				g_hDLL, GetWindowThreadProcessId(hWnd, NULL));

	if (uMsg == 0)
		uMsg = RegisterWindowMessage(lpMsgId);

	return g_hHook;
}

// The following several routines all inject "ourself" into a remote process
// and performs some work.

int GetLVItemText(HWND hWnd, int iItem, int iColumn, char *lpString)
{
	char szItem[MAX_DATA_BUF+1] = "";
	R_ListView_GetItemText(hWnd, iItem, iColumn, szItem, MAX_DATA_BUF);
	lstrcpyn( lpString, szItem , sizeof(szItem));
	return strlen(lpString);
}

BOOL SelLVItem(HWND hWnd, int iItem, BOOL bMulti)
{
	int iCount = ListView_GetItemCount(g_hWnd);
	// Clear out any previous selections if needed
	if (!bMulti && ListView_GetSelectedCount(hWnd) > 0) {
		for (int i = 0; i < iCount; i++) {
			R_ListView_SetItemState(hWnd, i, 0, LVIS_SELECTED);
		}
	}
	// Select item
	R_ListView_SetItemState(hWnd, iItem, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
	g_bRetVal = ListView_EnsureVisible(g_hWnd, iItem, FALSE);

	return g_bRetVal;
}

BOOL SelLVItemText(HWND hWnd, char *lpItem, BOOL bMulti)
{
	BOOL RetVal=FALSE;
	char szItem[MAX_DATA_BUF+1] = "";
	int iCount = ListView_GetItemCount(hWnd);
	// Clear out any previous selections if needed
	if (!bMulti && ListView_GetSelectedCount(hWnd) > 0) {
		for (int i = 0; i < iCount; i++) {
			R_ListView_SetItemState(hWnd, i, 0, LVIS_SELECTED);
		}
	}
	// Look for item
	for (int i = 0; i < iCount; i++) {
		R_ListView_GetItemText(hWnd, i, 0, szItem, MAX_DATA_BUF);
		if (lstrcmpi(lpItem, szItem) == 0) {
			// Found it, select it
			R_ListView_SetItemState(hWnd, i, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
			RetVal = ListView_EnsureVisible(hWnd, i, FALSE);
			break;
		}
	}
	lstrcpy(g_szBuffer, lpItem);

	return RetVal;
}

BOOL IsLVItemSel(HWND hWnd, char *lpItem)
{
	BOOL RetVal = FALSE; // Assume false
	char szItem[MAX_DATA_BUF+1] = "";
	int iCount = ListView_GetItemCount(hWnd);
	// Are there any selected?
	if (ListView_GetSelectedCount(hWnd) > 0) {
		// Look for item
		for (int i = 0; i < iCount; i++) {
			R_ListView_GetItemText(hWnd, i, 0, szItem, MAX_DATA_BUF);
			if (lstrcmpi(lpItem, szItem) == 0) {
				// Found it, determine if currently selected
				if (ListView_GetItemState(hWnd, i, LVIS_SELECTED) & LVIS_SELECTED) {
					RetVal = TRUE;
				}
			}
		}
	}

	return RetVal;
}

BOOL SelTVItemPath(HWND hWnd, char *lpPath)
{
	if (SetHook(hWnd, WM_TV_SELBYPATH, "WM_TV_SELBYPATH_RM") == NULL)
		return FALSE;

	lstrcpy(g_szBuffer, lpPath);
	SendMessage(hWnd, WM_TV_SELBYPATH, 0, 0);
	return g_bRetVal;
}

int GetTVSelPath(HWND hWnd, char *lpPath)
{
	if (SetHook(hWnd, WM_TV_GETSELPATH, "WM_TV_GETSELPATH_RM") == NULL)
		return FALSE;

	SendMessage(hWnd, WM_TV_GETSELPATH, 0, 0);
	lstrcpy(lpPath, g_szBuffer);

	return (int)strlen(lpPath);
}

int GetTCItemText(HWND hWnd, int iItem, char *lpString)
{
	if (SetHook(hWnd, WM_TC_GETTEXT, "WM_TC_GETTEXT_RM") == NULL) {
		*lpString = NUL;
		return 0;
	}

	SendMessage(hWnd, WM_TC_GETTEXT, iItem, 0);
	lstrcpy(lpString, g_szBuffer);

	return (int)strlen(lpString);
}

BOOL SelTCItem(HWND hWnd, int iItem)
{
	if (SetHook(hWnd, WM_TC_SELBYINDEX, "WM_TC_SELBYINDEX_RM") == NULL)
		return FALSE;

	SendMessage(hWnd, WM_TC_SELBYINDEX, iItem, 0);
	return g_bRetVal;
}

BOOL SelTCItemText(HWND hWnd, char *szText)
{
	if (SetHook(hWnd, WM_TC_SELBYTEXT, "WM_TC_SELBYTEXT_RM") == NULL)
		return FALSE;

	lstrcpy(g_szBuffer, szText);
	SendMessage(hWnd, WM_TC_SELBYTEXT, 0, 0);
	return g_bRetVal;
}


BOOL IsTCItemSel(HWND hWnd, char *lpItem)
{
	if (SetHook(hWnd, WM_TC_ISSEL, "WM_TC_ISSEL_RM") == NULL)
		return FALSE;

	lstrcpy(g_szBuffer, lpItem);
	SendMessage(hWnd, WM_TC_ISSEL, 0, 0);

	return g_bRetVal;
}

int GetTCItemCount(HWND hWnd)
{
	return TabCtrl_GetItemCount(hWnd);
}


/*
 * Piotr Kaluski <pkaluski@piotrkaluski.com>
 *
 * WaitForWindowInputIdle is a wrapper for WaitForInputIdle Win32 function.
 * The function waits until the application is ready to accept input
 * (keyboard keys, mouse clicks). It is useful, for actions, which take a long
 * time to complete. Instead of putting sleeps of arbitrary length, we can just
 * wait until the application is ready to respond. Original function takes
 * a process handle as an input. However, in GUI tests we more often operate
 * on windows then on applications.
 * NOTE: Unfortunatelly, this function not always works, so before using
 * it, check that is works in your environment
 *
 */
DWORD WaitForWindowInputIdle( HWND hwnd, DWORD milliseconds )
{
    DWORD pid = 0;
    DWORD dwThreadId = GetWindowThreadProcessId( hwnd, &pid );
    HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, TRUE, pid );
    if( hProcess == NULL ){
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
        warn( "OpenProcess failed with error %d: %s",
                dw, lpMsgBuf );
    }
    //printf( "Calling WaitForInputIdle for pid %ld\n", pid );
    DWORD result = WaitForInputIdle( hProcess, milliseconds );
    if( result == WAIT_FAILED ){
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
        warn( "WaitForInputIdle failed with error %d: %s",
                dw, lpMsgBuf );
    }else{
        if( result == WAIT_TIMEOUT ){
    //        printf( "WaitForInputIdle returned after TIME OUT" );
        }
        if( result == 0 ){
    //        printf( "WaitForInputIdle returned after wait was satisfied" );
        }
    }

    return result;
}


/* Wrapper around kebyd_event */
void KeyUp(UINT vk)
{
    BYTE scan = MapVirtualKey(vk, 0);
    keybd_event(vk, scan, KEYEVENTF_KEYUP, 0);
}

void KeyDown(UINT vk)
{
    BYTE scan=MapVirtualKey(vk, 0);
    keybd_event(vk, scan, 0, 0);
}


typedef struct windowtable {
    int size;
    HWND* windows/*[1024]*/;
} windowtable;


BOOL CALLBACK AddWindowChild(
    HWND hwnd,    // handle to child window
    LPARAM lParam // application-defined value
    )
{
    HWND* grow;
    windowtable* children = (windowtable*)lParam;
    /* Need to grow the table to make space for the next entry */
    if (children->windows)
        grow = (HWND*)saferealloc(children->windows, (children->size+1)*sizeof(HWND));
    else
        grow = (HWND*)safemalloc((children->size+1)*sizeof(HWND));
    if (grow == 0)
        return FALSE;
    children->windows = grow;
    children->size++;
    children->windows[children->size-1] = hwnd;
    return TRUE;
}

/*

Phill Wolf <pbwolf@bellatlantic.net>

Although mouse_event is documented to take a unit of "pixels" when moving
to an absolute location, and "mickeys" when moving relatively, on my
system I can see that it takes "mickeys" in both cases.  Giving
mouse_event an absolute (x,y) position in pixels results in the cursor
going much closer to the top-left of the screen than is intended.

Here is the function I have used in my own Perl modules to convert from screen coordinates to mickeys.

*/

#define SCREEN_TO_MICKEY(COORD,val) MulDiv((val)+1, 0x10000, GetSystemMetrics(SM_C ## COORD ## SCREEN))-1

void ScreenToMouseplane(POINT *p)
{
    p->x = SCREEN_TO_MICKEY(X,p->x);
    p->y = SCREEN_TO_MICKEY(Y,p->y);
}


/*  Same as mouse_event but without wheel and with time-out.
 */
VOID simple_mouse(
  DWORD dwFlags, // flags specifying various motion/click variants
  DWORD dx,      // horizontal mouse position or position change
  DWORD dy      // vertical mouse position or position change
 )
{
    char dstr[256];
    sprintf(dstr, "simple_mouse(%d, %d, %d)\n", dwFlags, dx, dy);
    OutputDebugString(dstr);
    mouse_event(dwFlags, dx, dy, 0, 0);
    Sleep (10);
}

/* JJ Utilities for thread-specific window functions */

BOOL AttachWin(HWND hwnd, BOOL fAttach)
{
  DWORD dwThreadId = GetWindowThreadProcessId(hwnd, NULL);
  DWORD dwMyThread = GetCurrentThreadId();
  return AttachThreadInput(dwMyThread, dwThreadId, fAttach);
}


SV*
GetTextHelper(HWND hwnd, int index, UINT lenmsg, UINT textmsg)
{
    SV* sv = 0;
    int len = SendMessage(hwnd, lenmsg, index, 0L);
    char* text = (char*)safemalloc(len+1);
    if (text != 0) {
        SendMessage(hwnd, textmsg, index, (LPARAM)text);
        sv = newSVpv(text, len);
        safefree(text);
    }
    return sv;
}

/*
 * Piotr Kaluski <pkaluski@piotrkaluski.com>
 *
 * OpenProcessForWindow opens a process, which is an owner of a window
 * identified by hWnd.
 *
 */
HANDLE OpenProcessForWindow( HWND hWnd )
{
    DWORD pid = 0;
    DWORD dwThreadId = GetWindowThreadProcessId( hWnd, &pid );
    HANDLE hProcess = OpenProcess( PROCESS_ALL_ACCESS, TRUE, pid );
    if( hProcess == NULL ){
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
        warn( "OpenProcess failed with error %d: %s",
                dw, lpMsgBuf );
    }
    return hProcess;
}

////////////////////////////////////////////////////////////////////////////
HWND PopupHandleGet(HWND hWnd, int x, int y, int wait) {
    g_popup = 0;
    if (SetHook(hWnd, WM_INITMENUPOPUPX, "WM_INITMENUPOPUP_RM") == NULL)
        return 0;
    int mickey_x = SCREEN_TO_MICKEY(X,x);
    int mickey_y = SCREEN_TO_MICKEY(Y,y);
    simple_mouse(MOUSEEVENTF_MOVE|MOUSEEVENTF_ABSOLUTE, mickey_x, mickey_y);
    simple_mouse(MOUSEEVENTF_RIGHTDOWN, 0, 0);
    simple_mouse(MOUSEEVENTF_RIGHTUP,   0, 0);
    Sleep(wait);
    if (g_popup == 0)
        UnhookWindowsHookEx(g_hHook);
    return g_popup;
}


MODULE = Win32::GuiTest		PACKAGE = Win32::GuiTest

PROTOTYPES: DISABLE

######################################################################
# Allocates memory in address space of a process, which owns window
# hWnd.
#
######################################################################

void
AllocateVirtualBufferImp( hWnd, memSize )
    HWND hWnd
    SIZE_T memSize
PPCODE:
    HANDLE hProcess = OpenProcessForWindow( hWnd );
	LPVOID pBuffer = VirtualAllocEx( hProcess, NULL, memSize,
		           MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if( pBuffer == NULL ){
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
        die( "VirtualAllocEx failed with error %d: %s",
                dw, lpMsgBuf );
    }else{
        XPUSHs( sv_2mortal( newSVuv( ( UV )pBuffer ) ) );
        XPUSHs( sv_2mortal( newSVuv( ( UV )hProcess ) ) );
    }


######################################################################
# Frees memory allocated by AllocateVirtualBuffer.
#
######################################################################

void
FreeVirtualBufferImp( hProcess, pBuffer )
    HANDLE hProcess
    LPVOID pBuffer
    PPCODE:
	    BOOL result = VirtualFreeEx( hProcess,
                                     pBuffer,
                                       0,
                                       0x8000 );
        if( !result ){
            LPVOID lpMsgBuf;
            DWORD dw = GetLastError();
            FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                dw,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR) &lpMsgBuf,
                0, NULL );
            die( "VirtualFreeEx failed with error %d: %s",
                    dw, lpMsgBuf );
        }


######################################################################
# Read from memory allocated by AllocateVirtualBuffer.
#
######################################################################

void
ReadFromVirtualBufferImp( hProcess, pVirtBuffer, memSize )
    HANDLE hProcess
    LPVOID pVirtBuffer
    SIZE_T memSize
PPCODE:
    SIZE_T copied = 0;
    char *pLocBuff = ( char *)safemalloc( memSize + 1 );
    if( !ReadProcessMemory( hProcess,
                            pVirtBuffer,
                            pLocBuff,
                            memSize,
                            &copied ) )
    {
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
        die( "ReadProcessMemory failed with error %d: %s",
                dw, lpMsgBuf );
    }else{
        XPUSHs( sv_2mortal( newSVpv( pLocBuff, memSize ) ) );
        safefree( pLocBuff );
    }


######################################################################
# Write to memory allocated by AllocateVirtualBuffer.
#
######################################################################

void
WriteToVirtualBufferImp( hProcess, pVirtBuffer, value )
    HANDLE hProcess
    LPVOID pVirtBuffer
    SV* value
PPCODE:
    SIZE_T copied = 0;
    STRLEN memSize = 0;
    char* pLocBuffer = SvPV( value, memSize );
    if( !WriteProcessMemory( hProcess,
                             pVirtBuffer,
                             pLocBuffer,
                             memSize,
                             &copied ) )
    {
        LPVOID lpMsgBuf;
        DWORD dw = GetLastError();
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR) &lpMsgBuf,
            0, NULL );
        die( "WriteProcessMemory failed with error %d: %s",
                dw, lpMsgBuf );
    }


SV*
GetListViewItem(hWnd, iItem, iSubItem )
	HWND hWnd
	int iItem
    int iSubItem
CODE:
	char szItem[MAX_DATA_BUF+1] = "";
	GetLVItemText(hWnd, iItem, iSubItem, szItem);
	RETVAL = newSVpv(szItem, 0);
OUTPUT:
	RETVAL

int
GetListViewItemCount(hWnd)
	HWND hWnd
CODE:
	RETVAL = ListView_GetItemCount(hWnd);
OUTPUT:
	RETVAL

BOOL
SelListViewItem(hWnd, iItem, bMulti=FALSE)
	HWND hWnd
	int iItem
	BOOL bMulti
CODE:
	RETVAL = SelLVItem(hWnd, iItem, bMulti);
OUTPUT:
	RETVAL

BOOL
SelListViewItemText(hWnd, lpItem, bMulti=FALSE)
	HWND hWnd
	char *lpItem
	BOOL bMulti
CODE:
	RETVAL = SelLVItemText(hWnd, lpItem, bMulti);
OUTPUT:
	RETVAL

BOOL
IsListViewItemSel(hWnd, lpItem)
	HWND hWnd
	char *lpItem
CODE:
	RETVAL = IsLVItemSel(hWnd, lpItem);
OUTPUT:
	RETVAL

HWND
GetListViewHeader(hWnd)
	HWND hWnd
CODE:
	RETVAL = ListView_GetHeader(hWnd);
OUTPUT:
	RETVAL

int
GetHeaderColumnCount(hWnd)
	HWND hWnd
CODE:
	RETVAL = Header_GetItemCount(hWnd);
OUTPUT:
	RETVAL

void
GetTabItems(hWnd)
	HWND hWnd
PPCODE:
	char szItem[MAX_DATA_BUF+1] = "";
	int iCount = GetTCItemCount(hWnd);
	for (int i = 0; i < iCount; i++) {
		GetTCItemText(hWnd, i, szItem);
	        XPUSHs(sv_2mortal(newSVpv(szItem, 0)));
	}

BOOL
SelTabItem(hWnd, iItem)
	HWND hWnd
	int iItem
CODE:
	RETVAL = SelTCItem(hWnd, iItem);
OUTPUT:
	RETVAL

BOOL
SelTabItemText(hWnd, lpItem)
	HWND hWnd
	char *lpItem
CODE:
	RETVAL = SelTCItemText(hWnd, lpItem);
OUTPUT:
	RETVAL

BOOL
IsTabItemSel(hWnd, lpItem)
	HWND hWnd
	char *lpItem
CODE:
	RETVAL = IsTCItemSel(hWnd, lpItem);
OUTPUT:
	RETVAL

SV*
GetTreeViewSelPath(hWnd)
	HWND hWnd
CODE:
	char szPath[MAX_DATA_BUF+1] = "";
	int len = GetTVSelPath(hWnd, szPath);
	RETVAL = newSVpv(szPath, len);
OUTPUT:
	RETVAL

void
GetCursorPos()
INIT:
  POINT pt;
PPCODE:
  pt.x = pt.y = -1;
  GetCursorPos(&pt);
  XPUSHs(sv_2mortal(newSVnv(pt.x)));
  XPUSHs(sv_2mortal(newSVnv(pt.y)));


void
SendLButtonUp()
    CODE:
    simple_mouse(MOUSEEVENTF_LEFTUP, 0, 0);

void
SendLButtonDown()
	CODE:
        simple_mouse(MOUSEEVENTF_LEFTDOWN, 0, 0);

void
SendMButtonUp()
	CODE:
        simple_mouse(MOUSEEVENTF_MIDDLEUP, 0, 0);

void
SendMButtonDown()
	CODE:
        simple_mouse(MOUSEEVENTF_MIDDLEDOWN, 0, 0);

void
SendRButtonUp()
	CODE:
        simple_mouse(MOUSEEVENTF_RIGHTUP, 0, 0);

void
SendRButtonDown()
	CODE:
        simple_mouse(MOUSEEVENTF_RIGHTDOWN, 0, 0);

void
SendMouseMoveRel(x,y)
    int x;
    int y;
	CODE:
        simple_mouse(MOUSEEVENTF_MOVE, x, y);

void
SendMouseMoveAbs(x,y)
	int x;
    int y;
	CODE:
        simple_mouse(MOUSEEVENTF_MOVE|MOUSEEVENTF_ABSOLUTE, x, y);

void
MouseMoveAbsPix(x,y)
    int x;
    int y;
PREINIT:
    int mickey_x = SCREEN_TO_MICKEY(X,x);
    int mickey_y = SCREEN_TO_MICKEY(Y,y);
CODE:
    simple_mouse(MOUSEEVENTF_MOVE|MOUSEEVENTF_ABSOLUTE, mickey_x, mickey_y);


#ifndef WHEEL_DELTA
#define WHEEL_DELTA 120
#endif
void
MouseMoveWheel(dwChange)
    DWORD dwChange
CODE:
    mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (dwChange*WHEEL_DELTA), 0);

HWND
GetDesktopWindow()
    CODE:
        RETVAL = GetDesktopWindow();
    OUTPUT:
        RETVAL


HWND
GetWindow(hwnd, uCmd)
    HWND hwnd
    UINT uCmd
    CODE:
        RETVAL = GetWindow(hwnd, uCmd);
    OUTPUT:
	RETVAL

SV*
GetWindowText(hwnd)
    HWND hwnd
    CODE:
        char text[512];
        int r;
	if ( unicode_semantics) {
		WCHAR buf[256];
        	r = GetWindowTextW(hwnd, buf, 255);
        	r = WideCharToMultiByte(CP_UTF8, 0, buf, r, text, 511, NULL, NULL);
        	RETVAL = newSVpvn(text, r);
		SvUTF8_on( RETVAL);
	} else {
        	r = GetWindowText(hwnd, text, 255);
        	RETVAL = newSVpvn(text, r);
	}
    OUTPUT:
        RETVAL

SV*
GetClassName(hwnd)
    HWND hwnd
    CODE:
//        SV* sv;
        char text[255];
        int r;
        r = GetClassName(hwnd, text, 255);
        RETVAL = newSVpv(text, r);
    OUTPUT:
        RETVAL

HWND
GetParent(hwnd)
    HWND hwnd
    CODE:
        RETVAL = GetParent(hwnd);
    OUTPUT:
        RETVAL

long
GetWindowLong(hwnd, index)
    HWND hwnd
    int index
    CODE:
        RETVAL = GetWindowLong(hwnd, index);
    OUTPUT:
        RETVAL


BOOL
SetForegroundWindow(hWnd)
    HWND hWnd
    CODE:
        RETVAL = SetForegroundWindow(hWnd);
    OUTPUT:
        RETVAL

HWND
SetFocus(hWnd)
    HWND hWnd
    CODE:
  		AttachWin(hWnd, TRUE);
        RETVAL = SetFocus(hWnd);
		AttachWin(hWnd, FALSE);
    OUTPUT:
        RETVAL

void
GetChildWindows(hWnd)
    HWND hWnd;
    PREINIT:
        windowtable children;
        int i;
    PPCODE:
        children.size    = 0;
        children.windows = 0;
        EnumChildWindows(hWnd, (WNDENUMPROC)AddWindowChild, (LPARAM)&children);
        for (i = 0; i < children.size; i++) {
            XPUSHs(sv_2mortal(newSViv((IV)children.windows[i])));
        }
	safefree(children.windows);


SV*
WMGetText(hwnd)
    HWND hwnd
    CODE:
//        SV* sv;
        char* text;
        int len = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0L);
        text = (char*)safemalloc(len+1);
        if (text != 0) {
            SendMessage(hwnd, WM_GETTEXT, (WPARAM)len + 1, (LPARAM)text);
            RETVAL = newSVpv(text, len);
            safefree(text);
        } else {
            RETVAL = 0;
        }
    OUTPUT:
        RETVAL

int
WMSetText(hwnd, text)
  HWND hwnd
  char * text
CODE:
  RETVAL = SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM) text);
OUTPUT:
  RETVAL

BOOL
IsChild(hWndParent, hWnd)
    HWND hWndParent
    HWND hWnd
    CODE:
        RETVAL = IsChild(hWndParent, hWnd);
    OUTPUT:
        RETVAL

DWORD
GetChildDepth(hAncestor, hChild)
    HWND hAncestor
    HWND hChild
    PREINIT:
        DWORD depth = 1;
    CODE:
        while ((hChild = GetParent(hChild)) != 0) {
            depth++;
            if (hChild == hAncestor) {
                break;
            }
        }
        RETVAL = depth;
    OUTPUT:
        RETVAL

int
SendMessage(hwnd, msg, wParam, lParam)
  HWND hwnd
  UINT msg
  WPARAM wParam
  LPARAM lParam
CODE:
  RETVAL = SendMessage(hwnd, msg, wParam, lParam);
OUTPUT:
  RETVAL

int
PostMessage(hwnd, msg, wParam, lParam)
  HWND hwnd
  UINT msg
  WPARAM wParam
  LPARAM lParam
CODE:
  RETVAL = PostMessage(hwnd, msg, wParam, lParam);
OUTPUT:
  RETVAL

void
CheckButton(hwnd)
    HWND hwnd
CODE:
    SendMessage(hwnd, BM_SETCHECK, BST_CHECKED, 0);

void
UnCheckButton(hwnd)
    HWND hwnd
CODE:
    SendMessage(hwnd, BM_SETCHECK, BST_UNCHECKED, 0);

void
GrayOutButton(hwnd)
    HWND hwnd
CODE:
    SendMessage(hwnd, BM_SETCHECK, BST_INDETERMINATE, 0);

BOOL
IsCheckedButton(hwnd)
    HWND hwnd
CODE:
    RETVAL = SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED;
OUTPUT:
    RETVAL

BOOL
IsGrayedButton(hwnd)
    HWND hwnd
CODE:
    RETVAL = SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_INDETERMINATE;
OUTPUT:
    RETVAL

BOOL
IsWindow(hwnd)
    HWND hwnd
CODE:
    RETVAL = IsWindow(hwnd);
OUTPUT:
    RETVAL

void
ScreenToClient(hwnd, x, y)
    HWND hwnd
    int x
    int y
INIT:
    POINT pt;
PPCODE:
    pt.x = x;
    pt.y = y;
    if (ScreenToClient(hwnd, &pt)) {
        XPUSHs(sv_2mortal(newSViv((IV)pt.x)));
        XPUSHs(sv_2mortal(newSViv((IV)pt.y)));
    }

void
ClientToScreen(hwnd, x, y)
    HWND hwnd
    int x
    int y
INIT:
    POINT pt;
PPCODE:
    pt.x = x;
    pt.y = y;
    if (ClientToScreen(hwnd, &pt)) {
        XPUSHs(sv_2mortal(newSViv((IV)pt.x)));
        XPUSHs(sv_2mortal(newSViv((IV)pt.y)));
    }

void
GetCaretPos(hwnd)
  HWND hwnd
INIT:
  POINT pt;
PPCODE:
  AttachWin(hwnd, TRUE);
  pt.x = pt.y = -1;
  if (GetCaretPos(&pt))
  {
    XPUSHs(sv_2mortal(newSVnv(pt.x)));
    XPUSHs(sv_2mortal(newSVnv(pt.y)));
  }
  AttachWin(hwnd, FALSE);

HWND
GetFocus(hwnd)
  HWND hwnd;
CODE:
  AttachWin(hwnd, TRUE);
  RETVAL = GetFocus();
  AttachWin(hwnd, FALSE);
OUTPUT:
  RETVAL

HWND
GetActiveWindow(hwnd)
  HWND hwnd;
CODE:
  AttachWin(hwnd, TRUE);
  RETVAL = GetActiveWindow();
  AttachWin(hwnd, FALSE);
OUTPUT:
  RETVAL

HWND
GetForegroundWindow()
CODE:
  RETVAL = GetForegroundWindow();
OUTPUT:
  RETVAL

HWND
SetActiveWindow(hwnd)
  HWND hwnd;
CODE:
  AttachWin(hwnd, TRUE);
  RETVAL = SetActiveWindow(hwnd);
  AttachWin(hwnd, FALSE);
OUTPUT:
  RETVAL

BOOL
EnableWindow(hwnd, fEnable)
  HWND hwnd
  BOOL fEnable
CODE:
  RETVAL = EnableWindow(hwnd, fEnable);
OUTPUT:
  RETVAL

BOOL
IsWindowEnabled(hwnd)
  HWND hwnd
CODE:
  RETVAL = IsWindowEnabled(hwnd);
OUTPUT:
  RETVAL

BOOL
IsWindowVisible(hwnd)
  HWND hwnd
CODE:
  RETVAL = IsWindowVisible(hwnd);
OUTPUT:
  RETVAL

BOOL
ShowWindow(hwnd, nCmdShow)
  HWND hwnd
  int nCmdShow
CODE:
  AttachWin(hwnd, TRUE);
  RETVAL = ShowWindow(hwnd, nCmdShow);
  AttachWin(hwnd, FALSE);
OUTPUT:
  RETVAL

BOOL
UnicodeSemantics(...)
CODE:
  switch( items) {
  case 0:
    break;
  case 1:
    unicode_semantics = SvTRUE( ST( 0));
    break;
  default:
    croak("Format: UnicodeSemantics() or UnicodeSemantics(BOOL)");
  }
  RETVAL = unicode_semantics;
OUTPUT:
  RETVAL

void
ScreenToNorm(x,y)
    int x;
    int y;
    PPCODE:
        x = SCREEN_TO_MICKEY(X,x);
        y = SCREEN_TO_MICKEY(Y,y);
        XPUSHs(sv_2mortal(newSViv((IV)x)));
        XPUSHs(sv_2mortal(newSViv((IV)y)));


void
NormToScreen(x,y)
    int x;
    int y;
    PPCODE:
        x = MulDiv(x + 1, GetSystemMetrics(SM_CXSCREEN), 65536) - 1;
        y = MulDiv(y + 1, GetSystemMetrics(SM_CYSCREEN), 65536) - 1;
        XPUSHs(sv_2mortal(newSViv((IV)x)));
        XPUSHs(sv_2mortal(newSViv((IV)y)));

void
GetScreenRes()
    PREINIT:
        int hor,ver;
    PPCODE:
        hor = GetSystemMetrics(SM_CXSCREEN);
        ver = GetSystemMetrics(SM_CYSCREEN);
        XPUSHs(sv_2mortal(newSViv((IV)hor)));
        XPUSHs(sv_2mortal(newSViv((IV)ver)));

void
GetWindowRect(hWnd)
    HWND hWnd;
    PREINIT:
        RECT rect;
    PPCODE:
        GetWindowRect(hWnd,&rect);
        XPUSHs(sv_2mortal(newSViv((IV)rect.left)));
        XPUSHs(sv_2mortal(newSViv((IV)rect.top)));
        XPUSHs(sv_2mortal(newSViv((IV)rect.right)));
        XPUSHs(sv_2mortal(newSViv((IV)rect.bottom)));



SV*
GetComboText(hwnd, index)
    HWND hwnd;
    int index
    CODE:
        RETVAL = GetTextHelper(hwnd, index, CB_GETLBTEXTLEN, CB_GETLBTEXT);
    OUTPUT:
        RETVAL

SV*
GetListText(hwnd, index)
    HWND hwnd;
    int index
    CODE:
        RETVAL = GetTextHelper(hwnd, index, LB_GETTEXTLEN, LB_GETTEXT);
    OUTPUT:
        RETVAL

void
GetComboContents(hWnd)
    HWND hWnd;
PPCODE:
    int nelems = SendMessage(hWnd, CB_GETCOUNT, 0, 0);
    int i;
    for (i = 0; i < nelems; i++) {
        XPUSHs(sv_2mortal(GetTextHelper(hWnd, i, CB_GETLBTEXTLEN, CB_GETLBTEXT)));
    }

BOOL
SelComboItem(hWnd, iItem)
	HWND hWnd;
	int iItem;
CODE:
	RETVAL = (SendMessage(hWnd, CB_SETCURSEL, iItem, 0) != CB_ERR);
OUTPUT:
	RETVAL

BOOL
SelComboItemText(hWnd, lpItem)
	HWND hWnd;
	char *lpItem;
CODE:
    int nelems = SendMessage(hWnd, CB_GETCOUNT, 0, 0);
	int i;
	RETVAL = FALSE;
	for (i = 0; i < nelems; i++) {
		SV *sv = GetTextHelper(hWnd, i, CB_GETLBTEXTLEN, CB_GETLBTEXT);
		char *txt = sv_2pvbyte_nolen(sv);
		if (lstrcmpi(txt, lpItem) == 0) {
			RETVAL = (SendMessage(hWnd, CB_SETCURSEL, i, 0) != CB_ERR);
			break;
		}
	}
OUTPUT:
	RETVAL


#########################################################################
# Selects combo item, which starts with a given string
#
#########################################################################

DWORD
SelComboString( hWnd, lpItem, start_idx = 0 )
    HWND hWnd;
    char *lpItem;
    DWORD start_idx;
CODE:
    int result = 0;
    result = SendMessage(hWnd,
                         CB_SELECTSTRING,
                         start_idx,
                         LPARAM( lpItem ) );
    if( result == CB_ERR ){
        RETVAL = -1;
    }else{
        RETVAL = result;
    }
OUTPUT:
    RETVAL

void
GetListContents(hWnd)
    HWND hWnd;
PPCODE:
    int nelems = SendMessage(hWnd, LB_GETCOUNT, 0, 0);
    int i;
    for (i = 0; i < nelems; i++) {
        XPUSHs(sv_2mortal(GetTextHelper(hWnd, i, LB_GETTEXTLEN, LB_GETTEXT)));
    }


HMENU
GetSubMenu(hMenu, nPos)
    HMENU hMenu;
    int nPos;
CODE:
    RETVAL = GetSubMenu(hMenu, nPos);
OUTPUT:
    RETVAL

# experimental code by SZABGAB

void
GetMenuItemInfo(hMenu, uItem)
    HMENU hMenu;
    UINT uItem;
INIT:
    MENUITEMINFO minfo;
    char buff[256] = "";   /* Menu Data Buffer */
PPCODE:
    memset(buff, 0, sizeof(buff));
    minfo.cbSize = sizeof(MENUITEMINFO);
    minfo.fMask = MIIM_CHECKMARKS | MIIM_DATA | MIIM_TYPE | MIIM_STATE;
    minfo.dwTypeData = buff;
    minfo.cch = sizeof(buff);

    if (GetMenuItemInfo(hMenu, uItem, TRUE, &minfo)) {
        XPUSHs(sv_2mortal(newSVpv("type", 4)));
       	if (minfo.fType == MFT_STRING) {
            XPUSHs(sv_2mortal(newSVpv("string", 6)));
    	    int r;
    	    r = strlen(minfo.dwTypeData);
            XPUSHs(sv_2mortal(newSVpv("text", 4)));
            XPUSHs(sv_2mortal(newSVpv(minfo.dwTypeData, r)));
	} else if (minfo.fType == MFT_SEPARATOR) {
            XPUSHs(sv_2mortal(newSVpv("separator", 9)));
	} else {
            XPUSHs(sv_2mortal(newSVpv("unknown", 7)));
	}
        XPUSHs(sv_2mortal(newSVpv("fstate", 6)));
        XPUSHs(sv_2mortal(newSViv(minfo.fState)));
        XPUSHs(sv_2mortal(newSVpv("ftype", 5)));
        XPUSHs(sv_2mortal(newSViv(minfo.fType)));
    }

#void
#getLW(hWnd)
#    HWND hWnd;
#INIT:
#   CWnd myWnd;
#PPCODE:
#    myWnd = CWnd::FromHandle(hWnd);
#    XPUSHs(sv_2mortal(newSVpv("type", 4)));


int
GetMenuItemCount(hMenu)
    HMENU hMenu;
CODE:
    RETVAL = GetMenuItemCount(hMenu);
OUTPUT:
    RETVAL


int
GetMenuItemIndex(hm, sitem)
    HMENU hm;
    char *sitem;
CODE:
    int mi = 0;
    int mic = 0;
    MENUITEMINFO minfo;
    char buff[256] = ""; /* Menu Data Buffer */
    BOOL found = FALSE;

    RETVAL = -1;

    mic = GetMenuItemCount(hm);
    if (mic != -1) {
        /* Look at each item to determine if it is the one we want */
        for (mi = 0; mi < mic; mi++) {
	    /* Refresh menu item info structure */
	    memset(buff, 0, sizeof(buff));
	    minfo.cbSize = sizeof(MENUITEMINFO);
	    minfo.fMask = MIIM_DATA | MIIM_TYPE;
	    minfo.dwTypeData = buff;
	    minfo.cch = sizeof(buff);
	    if (GetMenuItemInfo(hm, mi, TRUE, &minfo) &&
                minfo.fType == MFT_STRING &&
                minfo.dwTypeData != NULL &&
                strncasecmp(minfo.dwTypeData, sitem, strlen(sitem)) == 0) {
                /* Got what we came for, so return index. */
                RETVAL = mi;
                break;
            }
	}
    }
OUTPUT:
    RETVAL

HMENU
GetSystemMenu(hWnd, bRevert)
    HWND hWnd;
    BOOL bRevert;
CODE:
    RETVAL = GetSystemMenu(hWnd, bRevert);
OUTPUT:
    RETVAL

UINT
GetMenuItemID(hMenu, nPos)
    HMENU hMenu;
    int nPos;
CODE:
    RETVAL = GetMenuItemID(hMenu, nPos);
OUTPUT:
    RETVAL

HMENU
GetMenu(hWnd)
    HWND hWnd;
CODE:
    RETVAL = GetMenu(hWnd);
OUTPUT:
    RETVAL


BOOL
SetWindowPos(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags)
  HWND hWnd;
  HWND hWndInsertAfter;
  int X;
  int Y;
  int cx;
  int cy;
  UINT uFlags;
CODE:
    RETVAL = SetWindowPos(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags);
OUTPUT:
    RETVAL

void
TabCtrl_SetCurFocus(hWnd, item)
    HWND hWnd
    int item
    CODE:
       TabCtrl_SetCurFocus(hWnd, item);

int
TabCtrl_GetCurFocus(hWnd)
    HWND hWnd
    CODE:
        RETVAL = TabCtrl_GetCurFocus(hWnd);
    OUTPUT:
        RETVAL

int
TabCtrl_SetCurSel(hWnd, item)
    HWND hWnd
    int item
    CODE:
        RETVAL = TabCtrl_SetCurSel(hWnd, item);
    OUTPUT:
        RETVAL

int
TabCtrl_GetItemCount(hWnd)
    HWND hWnd
    CODE:
        RETVAL = TabCtrl_GetItemCount(hWnd);
    OUTPUT:
        RETVAL

void
SendRawKey(vk, flags)
    UINT vk;
    DWORD flags;
CODE:
    BYTE scan = MapVirtualKey(vk, 0);
    keybd_event(vk, scan, flags, 0);

int
VkKeyScan(c)
	int c;
CODE:
	RETVAL = VkKeyScan((char) c);
OUTPUT:
	RETVAL

int
GetAsyncKeyState(c)
	int c;
CODE:
	RETVAL = GetAsyncKeyState(c);
OUTPUT:
	RETVAL

HWND
WindowFromPoint(x, y)
    int x;
    int y;
PREINIT:
    POINT pt;
CODE:
    pt.x = x;
    pt.y = y;
    RETVAL = WindowFromPoint(pt);
OUTPUT:
    RETVAL


#####################################################################
# Waits for input idle for the application, which owns the window
# hWnd. It is a wrapper around WaitForInputIdle Win32 API.
# Does not always work as expected, seams to be not that reliable
# mechanism. But it's better then nothing and there are still some
# cases when it works
#
######################################################################

DWORD
WaitForReady( hWnd, dwMilliseconds = 30000 )
    HWND hWnd;
    DWORD dwMilliseconds;
CODE:
    RETVAL = WaitForWindowInputIdle( hWnd, dwMilliseconds );
OUTPUT:
    RETVAL

############################################################################
HWND
GetPopupHandle(hWnd, x, y, wait=50)
    HWND hWnd;
    int x;
    int y;
    int wait;
CODE:
    RETVAL = PopupHandleGet(hWnd, x, y, wait);
OUTPUT:
    RETVAL
############################################################################


MODULE = Win32::GuiTest		PACKAGE = Win32::GuiTest::DibSect

PROTOTYPES: DISABLE

DibSect *
DibSect::new()

void
DibSect::DESTROY()

bool
DibSect::CopyClient(hwnd, rect=0)
  HWND hwnd
  SV  *rect
CODE:
  RECT r, *pr = 0;
  if (rect)
  {
    if (!(SvROK(rect) &&  (rect = SvRV(rect)) && SvTYPE(rect) == SVt_PVAV))
	    croak("Second argument to CopyClient() must be a reference to array");
    AV * av = (AV*) rect;
    int len = av_len(av) + 1;
    if (len != 4)
      croak("Rectangle requires 4 elements, not %d", len);
    pr = &r;
    SetRectEmpty(pr);
    LONG * p = (LONG *) pr;
    for(int i = 0 ; i < len; i++, p++)
    {
      SV ** psv = av_fetch(av, i, 0);
      if (psv)
        *p = SvIV(*psv);
    }
  }
  RETVAL = THIS->CopyWndClient(hwnd, pr) != 0;
OUTPUT:
  RETVAL

bool
DibSect::CopyWindow(hwnd)
  HWND hwnd
CODE:
  RECT r;
  GetWindowRect(hwnd, &r);
  RETVAL = THIS->CopyWndClient(GetDesktopWindow(), &r) != 0;
OUTPUT:
  RETVAL


bool
DibSect::SaveAs(szFile)
	char *	szFile
CODE:
	RETVAL = THIS->SaveAs(szFile);
OUTPUT:
RETVAL

bool
DibSect::Invert()
CODE:
	RETVAL = THIS->Invert();
OUTPUT:
RETVAL


bool
DibSect::ToGrayScale()
CODE:
	RETVAL = THIS->ToGrayScale();
OUTPUT:
RETVAL

bool
DibSect::Destroy()
CODE:
	RETVAL = THIS->Destroy();
OUTPUT:
RETVAL

bool
DibSect::ToClipboard()
CODE:
	RETVAL = THIS->ToClipboard();
OUTPUT:
RETVAL