// This is the main DLL file.

#include "stdafx.h"
#define DLLAPI_BUTTON_HOOK
#include "ButtonHook.h"
#include <psapi.h>

using namespace Ooganizer;

#ifdef _DEBUG
	#import "..\\..\\Target\\Debug\\Settings.tlb" raw_interfaces_only
#else
	#import "..\\..\\Target\\Release\\Settings.tlb" raw_interfaces_only
#endif
using namespace Settings;

#ifdef _DEBUG
	#import "..\\..\\Target\\Debug\\Platform.tlb" raw_interfaces_only
#else
	#import "..\\..\\Target\\Release\\Platform.tlb" raw_interfaces_only
#endif
using namespace Platform;

#ifdef _DEBUG
	#import "..\\..\\Target\\Debug\\ButtonWPForm.tlb"
#else
	#import "..\\..\\Target\\Release\\ButtonWPForm.tlb"
#endif
using namespace ButtonWPForm;

////
// ***** What type of Windows should have the Button *****
////
const DWORD WS_STYLES_TO_TEST_FOR = WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU;

////
// ***** Top-Level Dialogs *****
////
const DWORD WS_STYLES_TO_TEST_FOR_DIALOGS = WS_SYSMENU | WS_POPUP;

//////////////////////////////////////////////////////////////////////
//******************************************************************** 
// Macros and typedefs
//******************************************************************** 
//////////////////////////////////////////////////////////////////////

#define MSG_HOOK			( L"MSG_HOOK" )
#define MSG_UNHOOK			( L"MSG_UNHOOK" )

////
// Each DLL instance (All Instances) need to keeps track of the following
// Data members (these end up being the same values so no need to put them into the DS)
////
HINSTANCE s_hModule = NULL;
UINT m_WMHook = NULL;
UINT m_WMUnHook = NULL;

// enum to send MSG_HOOK and MSG_UNHOOK
typedef enum _MSG{_MSG_HOOK,_MSG_UNHOOK} MSG_;

////
// Each DLL instance (All instances not just the once whose window,
// we'll hook into) will hold a pointer to it's own 
// CWHPaccess Object. (allowing it access to the Shared Data Segment)
////
CWHPaccess* s_ButtonHookAccess = NULL;

////
// Each DLL instanc needs to keep track of it's recently closed windows
////
#define NCLOSES_TO_TRACK 25
// timeout values, we need to ignore activate messages for nTime when
// we unhook to avoid rehooking again in processes that have multiple windows
const int IGNORE_ACTIVATE_MESSAGE_TIMEOUT_MS = 150;
struct RecentWindowsWTimeouts
{
	HWND hWnd;
	DWORD dwTimeInserted;
};
RecentWindowsWTimeouts s_hRecentClosedWindows[NCLOSES_TO_TRACK] = {NULL,NULL};
UINT s_HRecentListI = 0;

//////////////////////////////////////////////////////////////////////
// Func: InsertRecentClosedWindow()
// Desc: Insert a recently closed window
//////////////////////////////////////////////////////////////////////
void InsertRecentClosedWindow(HWND hWnd)
{
  RecentWindowsWTimeouts recent;
  recent.hWnd = hWnd;
  recent.dwTimeInserted = GetTickCount();

  s_hRecentClosedWindows[s_HRecentListI] = recent;
  s_HRecentListI = s_HRecentListI + 1;

  if(s_HRecentListI == NCLOSES_TO_TRACK)
	 s_HRecentListI = 0;
}
//////////////////////////////////////////////////////////////////////
// Func: FindRecentClosedWindow()
// Desc: Find a recently closed window
//////////////////////////////////////////////////////////////////////
BOOL FindRecentClosedWindow(HWND hWnd)
{
	DWORD dwTickCount = GetTickCount();
	for (UINT i = 0; i < s_HRecentListI; ++i)
	{
		if(s_hRecentClosedWindows[i].hWnd == hWnd)
		{
			DWORD diffTime = dwTickCount - s_hRecentClosedWindows[i].dwTimeInserted;
			if(diffTime < IGNORE_ACTIVATE_MESSAGE_TIMEOUT_MS)
				return true;
			else
				return false;
		}
	}
	return false;
}
bool IsAWindowToHookInto(HWND hwnd);
//////////////////////////////////////////////////////////////////////
// Func: EnumProc()
// Desc: Hook/Unhook all top level windows. Called for every top-level
//		 Window on the screen. 
//
// Prms: lParam, if true send Hook Message, false send UnHook Msg
//       
// Retr: TRUE, always
//////////////////////////////////////////////////////////////////////
BOOL CALLBACK EnumProc( HWND hWnd, LPARAM lParam )
{
	switch (lParam)
	{
	case _MSG_HOOK:
		if((m_WMHook >= 0xC000) && (m_WMHook <= 0xFFFF) &&
		   (IsAWindowToHookInto(hWnd)))
		{			
		   log(LOGGING_DEBUG, L"Sending m_WMHook to hWnd %u Msg %u", hWnd, m_WMHook);
		   SendMessage( hWnd, m_WMHook, 0, 0 );
		}
		break;
	
	case _MSG_UNHOOK:
		if((m_WMUnHook >= 0xC000) && (m_WMUnHook <= 0xFFFF) &&			
			/* Check if Window is hooked into */
			( s_ButtonHookAccess->FindHookedWnd(hWnd) == TRUE ))
		{
			log(LOGGING_DEBUG, L"Sending m_WMUnHook to hWnd %u Msg %u", hWnd, m_WMHook);
			SendMessage( hWnd, m_WMUnHook, 0, 0 );
		}
		break;
	
	default:
		break;
	}

	return TRUE;
}
//////////////////////////////////////////////////////////////////////
// Func: GetProcessNameFromPID()
// Retr: Returns the process name in the buffer and true if everything went well,
//	     false otherwise.
//////////////////////////////////////////////////////////////////////
bool GetProcessNameFromHWND(HWND hwnd, wchar_t strProcessNameBuf[MAX_PATH + 1])
{
	DWORD pid = NULL;
	GetWindowThreadProcessId(hwnd,&pid);
    HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid );

    // Get the process name
    if (NULL != hProcess )
        GetModuleBaseName( hProcess, NULL, strProcessNameBuf, (MAX_PATH + 1));

    CloseHandle(hProcess);
	return true;
}
//////////////////////////////////////////////////////////////////////
// Func: IsWhiteListed()
// Desc: Function allows us to Blacklist certain windows to not be
//		 included when hooking the WinProcButton Hook.
//		 a Window can be whitelisted either on Title or Type, passed in
//		 thru the configuration
//
// Prms: hwnd, handle to a window we want to check
//       
// Retr: true, if window is WhiteListed, false otherwise
//////////////////////////////////////////////////////////////////////
bool IsWhiteListed(HWND hwnd)
{			
	wchar_t strBuf[MAX_PATH + 1] = {0};

	if(GetProcessNameFromHWND(hwnd,strBuf))
	{
		_wcsupr_s(strBuf,MAX_PATH);
		if(s_ButtonHookAccess->IsAllowedProcessName(strBuf))
			return true;
	}

	if (GetWindowText(hwnd,strBuf,MAX_PATH))	
	{			
		_wcsupr_s(strBuf,MAX_PATH);
		if(s_ButtonHookAccess->IsAllowedWindowsTitle(strBuf))		
			return true;
	}	

	if(GetClassName(hwnd,strBuf,MAX_PATH))
	{
		_wcsupr_s(strBuf,MAX_PATH);
		if(s_ButtonHookAccess->IsAllowedWindowsClass(strBuf))
			return true;
	}

	return false;
}
//////////////////////////////////////////////////////////////////////
// Func: IsFileOpenOrFileSaveAsDialog()
// Desc: We also hook into FileOpen and FileSaveAs Dialogs(), in order
//		 to do so we compare the window class
//
// Retr: true, if dialog is of a fileopen/filesaveas class, false otherwise
//////////////////////////////////////////////////////////////////////
bool IsFileOpenOrFileSaveAsDialog(HWND hwnd)
{
	wchar_t strBuf[MAX_PATH + 1] = {0};
	if(GetClassName(hwnd,strBuf,MAX_PATH))
	{
		_wcsupr_s(strBuf);		
		if(lstrcmp(strBuf, L"#32770") == 0)
			return true;
	}
	return false;
}
//////////////////////////////////////////////////////////////////////
// Strc: FileNameAndLocationStruct()
// Desc: We use this to pass out location and file name by EnumFileNameAndLocationProc
//////////////////////////////////////////////////////////////////////
struct FileNameAndLocationStruct
{
	wchar_t location[MAX_PATH + 1];
	wchar_t filename[MAX_PATH + 1];
	wchar_t filetypes[MAX_PATH + 1];
};
//////////////////////////////////////////////////////////////////////
// Func: EnumFileNameAndLocationProc()
// Desc: Iterate all child windows and extract FileName and FileLocation
//		 
// Prms: lParam, a FileNameAndLocationStruct*
//       
// Retr: TRUE, always
//////////////////////////////////////////////////////////////////////
BOOL CALLBACK EnumFileNameAndLocationProc( HWND hWnd, LPARAM lParam )
{
	wchar_t strWindowText[MAX_PATH + 1] = {0};	
	wchar_t strClassName[MAX_PATH + 1] = {0};
	GetWindowText(hWnd, strWindowText, MAX_PATH);	
	GetClassName(hWnd, strClassName, MAX_PATH);

	bool bContinue = false;
	// We only care about the following 3 window classes
	// all others continue iteration	
	if((lstrcmp(L"Edit", strClassName) == 0) ||
	   (lstrcmp(L"ComboBox", strClassName) == 0) ||
	   (lstrcmp(L"ToolbarWindow32", strClassName) == 0))
	{
		bContinue = true;
	}

	// skip this window
	if(!bContinue)
		return TRUE; 

	// passed in file struct pointer
	FileNameAndLocationStruct* pstruct = (FileNameAndLocationStruct*) lParam;

	// Stop if there is no window text
	if(lstrlen(strWindowText) <= 0)
		return TRUE;
	
	// ignore any window texts that have an &
	if(wcsstr(strWindowText, L"&"))
		return TRUE;

	// * Good for Debugging *
	//log(LOGGING_DEBUG, L"Found Window Text - %s ", strWindowText);

	// File Types Window Text (pass out in case it's needed later)
	if(wcsstr(strWindowText, L"*."))
	{
		log(LOGGING_DEBUG, L"Found FileType Text %s", strWindowText);
		lstrcpy(pstruct->filetypes,strWindowText);
		return TRUE;
	}

	// Is this a location text
	if(wcsstr(strWindowText, L": ")) // This could be very much English Windows Specific (or not?, should check)
	{				
		// get the location of ": "
		int i = 0;
		wchar_t* pBegin = strWindowText;
		for (; i < MAX_PATH; ++i)
		{
			if((*pBegin == L':') && (*(pBegin + 1) == L' '))
			{
				pBegin = pBegin + 2; // start after the ": "
				break;
			}
			++pBegin;
		}
		
		if(pBegin != NULL)
		{
			log(LOGGING_DEBUG, L"Found Location Text %s", pBegin);
			lstrcpy(pstruct->location,pBegin);
		}
		return TRUE;
	}

	// illegal file name characters
	wchar_t* illegalfilechars = L"\\/:*?\"<>|";
	int illegalsize = lstrlen(illegalfilechars);

	if(wcsstr(strWindowText,L"."))
	{
		// check to see if string contains any illegal 
		// file name characters
		for(int i = 0; i < illegalsize; ++i)
		{
			wchar_t c[2] = {0};
			c[0] = illegalfilechars[i];

			// found illegal character, ignore string as file name
			if(wcsstr(strWindowText,c))
				return TRUE;
		}

		// Found a 100% file name string (it contains a '.')
		log(LOGGING_DEBUG, L"Found FileName Text %s", strWindowText);
		lstrcpy(pstruct->filename,strWindowText);
		return TRUE;
	}
	else if(lstrlen(strWindowText) >= 1)
	{
		// check to see if string contains any illegal 
		// file name characters
		for(int i = 0; i < illegalsize; ++i)
		{
			wchar_t c[2] = {0};
			c[0] = illegalfilechars[i];

			// found illegal character, ignore string as file name
			if(wcsstr(strWindowText,c))
				return TRUE;
		}
		
		// Found a possible Most likely file name string (must not contain a '.')
		log(LOGGING_DEBUG, L"Found FileName Text %s", strWindowText);
		lstrcpy(pstruct->filename,strWindowText);		
		return TRUE;
	}	

	return TRUE;
}
//////////////////////////////////////////////////////////////////////
// Func: GetFileNameAndLocationStringValuesFromDialogWindow()
// Desc: Iterates through all the child windows and get the values we need
//		 and pass them back out
//
// Prms: hwnd, handle the fileOpen dialog
//	     pCallerObj, WPFCaller object where we will write the retrieved information to
//////////////////////////////////////////////////////////////////////
bool GetFileNameAndLocationStringValuesFromDialogWindow(HWND hwnd, WPFCaller* pCallerObj)
{
	// Iterate all child Windows and parse all the texts, the enum function 
	// will do all the work
	if(hwnd && ::IsWindow(hwnd))
	{
		// Clear any old values in the caller object
		memset(pCallerObj->m_strFileNameLocationBuf, 0, sizeof(pCallerObj->m_strFileNameLocationBuf));
		memset(pCallerObj->m_strFileTypesBuf, 0, sizeof(pCallerObj->m_strFileTypesBuf));

		// Create temporary object to hold values
		FileNameAndLocationStruct filenameAndLocation;		
		memset(filenameAndLocation.filename, 0, sizeof(filenameAndLocation.filename));
		memset(filenameAndLocation.location, 0, sizeof(filenameAndLocation.location));
		memset(filenameAndLocation.filetypes, 0, sizeof(filenameAndLocation.filetypes));

		// Try to Resolve
		log(LOGGING_DEBUG, L"About to call EnumFileNameAndLocationProc (EnumChildWindows)");
		EnumChildWindows(hwnd, EnumFileNameAndLocationProc, (LPARAM) &filenameAndLocation );

		// get resolving lengths
		int nLen1 = lstrlen(filenameAndLocation.filename);
		int nLen2 = lstrlen(filenameAndLocation.location);
		int nLen3 = lstrlen(filenameAndLocation.filetypes);

		// Handle errors * Good for Debugging *
		/*
		if(nLen1 == 0)
			log(LOGGING_DEBUG, L"Error resolving - FileName - FileOpen or FileSaveAs Dialog");
		if(nLen2 == 0)
			log(LOGGING_DEBUG, L"Error resolving - FileLocation - FileOpen or FileSaveAs Dialog");
		if(nLen3 == 0)
			log(LOGGING_DEBUG, L"Error resolving - FileTypes - FileOpen or FileSaveAs Dialog");
		*/

		// Something went wrong! no point moving on
		if(nLen1 == 0 || nLen2 == 0 || nLen3 == 0)
			return false;

		////
		// Now Copy out the values to the buffers of the caller object that was passed in
		////
		lstrcpyn(pCallerObj->m_strFileNameLocationBuf, filenameAndLocation.location, MAX_PATH);
		lstrcat(pCallerObj->m_strFileNameLocationBuf, L"\\");
		lstrcat(pCallerObj->m_strFileNameLocationBuf, filenameAndLocation.filename);
		lstrcpyn(pCallerObj->m_strFileTypesBuf, filenameAndLocation.filetypes, MAX_PATH);
	
		// Log info to see what's going on
		log(LOGGING_DEBUG, L"GetFileNameAndLocation setting m_strFileNameLocattionBuf to %s", pCallerObj->m_strFileNameLocationBuf);
		log(LOGGING_DEBUG, L"GetFileNameAndLocation setting m_strFileTypesBuf to %s", pCallerObj->m_strFileTypesBuf);
		return true;

	}
	return false;
}
//////////////////////////////////////////////////////////////////////
// Func: IsAWindowToHookInto()
// Desc: Broad check to see if we want to hook into a window.
//		 Called by CallWndHookProc. 
//		 Window on the screen. 
//
// Prms: hwnd, handle to a window we want to check//       
// 
// Note: This function calls IsBlackListed() for more window exclusions.
// 
// Retr: true, if window is should be hooked into, false otherwise
//////////////////////////////////////////////////////////////////////
bool IsAWindowToHookInto(HWND hwnd)
{	
	DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
	DWORD dwExStyle = GetWindowLong(hwnd, GWL_EXSTYLE);		
			
	if (    /* top-raised windows */
			( ( dwExStyle & WS_EX_WINDOWEDGE ) == WS_EX_WINDOWEDGE )

		    /* Styles to test for (named above) */
		    && ( ( dwStyle & WS_STYLES_TO_TEST_FOR ) == WS_STYLES_TO_TEST_FOR ) 

		    /* Has no parent = top-level window */	 
		    && ( GetParent(hwnd) == NULL )									 

			/* Check if Window is not already hooked into*/
			&& ( s_ButtonHookAccess->FindHookedWnd(hwnd) == FALSE )

			/* Check if Window has not been recently closed in this process*/
			&& ( FindRecentClosedWindow(hwnd) == FALSE )

			// **** CUSTOM INCLUSION LIST  ****
	        && ( IsWhiteListed(hwnd) )

			// Make sure the window is visible to begin with * bug fix for ms word*
			&& ( ::IsWindowVisible(hwnd))
	   )
	{
		return true;
	}
	else if (	// We also want to hook into the FileOpen & FileSaveAs Dialog

			/* top-raised windows */
			( ( dwExStyle & WS_EX_WINDOWEDGE ) == WS_EX_WINDOWEDGE )

			/* Styles to test for (named above) */
			&& ( ( dwStyle & WS_STYLES_TO_TEST_FOR_DIALOGS ) == WS_STYLES_TO_TEST_FOR_DIALOGS ) 

			/* Has a parent = it is a top-level window with parent */	 
			&& ( GetParent(hwnd) != NULL )									 

			/* Check if Window's parent is hooked into */
			&& ( s_ButtonHookAccess->FindHookedWnd(GetParent(hwnd)) == TRUE )

			/* Check if Window is not already hooked into*/
			&& ( s_ButtonHookAccess->FindHookedWnd(hwnd) == FALSE )

			/* Check if Window has not been recently closed in this process*/
			&& ( FindRecentClosedWindow(hwnd) == FALSE )

			// **** CUSTOM INCLUSION LIST  ****
			&& ( IsWhiteListed(GetParent(hwnd)) )

			// Make sure the window's parent is visible to begin with * bug fix for ms word *
			&& ( ::IsWindowVisible(GetParent(hwnd)))

			// * We currently hook only into Top-Level FileOpen/SaveAs Dialogs
			&& ( IsFileOpenOrFileSaveAsDialog(hwnd) )
		)
	{
		return true;
	}
	else
	{
		return false;
	}
}
//////////////////////////////////////////////////////////////////////
// Func: HookWndProc()
// Desc: Function that implements the WndProc for every process that
//		 has the window we are looking for. This is code is shared within
//		 the process on every window in the system.
//        
// Note: This function is just the WndProc() Implementation
//	     All we have to do is process it if we care for it or pass it down
//		 to the next hook (that may be in the system).
//
// Prms:  hWnd, handle to window
//		  Msg, passed in message
//		  wParam, specifies additional message information
//		  lParam, specifies additional message information
//              
// Retr: LRESULT, 
//////////////////////////////////////////////////////////////////////
LRESULT CALLBACK MyWndProc( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam )
{	
	// default return
	LRESULT result = 0;
	
	// Get the HookWndItem Information
	BLANK_HookWndItem(item);
	item = s_ButtonHookAccess->GetHookedWndItem(hWnd);
	
	bool bCloseMsg = ((Msg == WM_NCDESTROY) || (Msg == WM_DESTROY) || (Msg == WM_CLOSE));
	if(bCloseMsg)
	   log(LOGGING_DEBUG, L"Received WM_NCDESTROY or DESTROY MESSAGE or WM_CLOSE for window %u", item.hWnd);

	// Is this is a Window that is hooked? --- Ignore FileOpen / SaveDialog (Deal with them below)
	if((item.hWnd != NULL) && (hWnd == item.hWnd) && !IsFileOpenOrFileSaveAsDialog(item.hWnd)) 
	{
		////
		// Create wpfcaller object  for this Window, if it doesn't exist
		// ~Every wpfcaller is mapped to a single HookWndItem
		////				
		if(item.wpfcaller == NULL)
		{	
			log(LOGGING_DEBUG, L"Creating a wpfCaller Instance for window %u", item.hWnd);
			item.wpfcaller = new WPFCaller(item, s_hModule);	
		}

		////
		// Case StopHook wants us to Unhook the WmProc Hook or
		// the Window gets Closed
		////			
		if ( (Msg == m_WMUnHook) ||	 bCloseMsg )
		{
			log(LOGGING_MEDIUM, L"Received WMUnook or bCloseMsg message for window %u", item.hWnd);
			
			// No Longer need to monitor this Window
			s_ButtonHookAccess->DeleteHookedWnd(item.hWnd);
			InsertRecentClosedWindow(item.hWnd);

			// Unhook Our WndProc Procedure (put the old one back)
			log(LOGGING_MEDIUM, L"Unhooking a Window %u from the HookWndProc", item.hWnd);
			SetWindowLong(hWnd,GWL_WNDPROC,(LONG) item.DefWndProc);

			// Delete this Window's CaptionButton Object
			if(item.wpfcaller != NULL)
			{				
				log(LOGGING_DEBUG, L"Deleting a wpfCaller Instance for Window %u", item.hWnd);
				delete item.wpfcaller;
			}

			// Call the old WindProc to handle Close message
			if(bCloseMsg)
				result = CallWindowProc( item.DefWndProc, hWnd, Msg, wParam, lParam );					
		}
		// All other Messages
		else
		{				
			// FOR TESTING - Skip Handling of Messages (Below)		
			// result = CallWindowProc( item->DefWndProc, hWnd, Msg, wParam, lParam );
			try
			{
				// the wpfCaller Handles all other Messages
				if(item.wpfcaller != NULL)
					result = item.wpfcaller->OnDefault( hWnd, Msg, wParam, lParam);
				else
					result = CallWindowProc( item.DefWndProc, hWnd, Msg, wParam, lParam);
			}
			catch(...)
			{
				log(LOGGING_LOW, L"*Error* Occured calling WpfCaller->OnDefault()");
			}
		}
	}
	else if((item.hWnd != NULL) && (hWnd == item.hWnd) && IsFileOpenOrFileSaveAsDialog(item.hWnd)) // Deal with FileOpen / FileSave Dialog Here
	{
		////
		// Case StopHook wants us to Unhook the WmProc Hook or
		// the Window gets Closed
		////			
		if ( (Msg == m_WMUnHook) ||	 bCloseMsg )
		{
			log(LOGGING_DEBUG, L"FileOpenOrFileSaveDialog() Received WMUnook or bCloseMsg message for Window %u", item.hWnd);
			
			// No Longer need to monitor this Window
			s_ButtonHookAccess->DeleteHookedWnd(item.hWnd);
			InsertRecentClosedWindow(item.hWnd);

			// Unhook Our WndProc Procedure (put the old one back)
			log(LOGGING_DEBUG, L"FileOpenOrFileSaveDialog() Unhooking from the HookWndProc for Window %u", item.hWnd);
			SetWindowLong(hWnd,GWL_WNDPROC,(LONG) item.DefWndProc);			

			// Call the old WindProc to handle Close message
			if(bCloseMsg)
			{
			   // Handle CLose Message
			   result = CallWindowProc( item.DefWndProc, hWnd, Msg, wParam, lParam );
			}
		}	
		else if((Msg == WM_COMMAND) && (LOWORD(wParam) == 1)) // Received Ok or Open Button
		{
			log(LOGGING_DEBUG, L"Reveived Command Message from Dialog Button %u - Ok or Open Button",LOWORD(wParam));
			// First Let's get the WPFCaller Instance of the main window and let it know that this occured				   
			BLANK_HookWndItem(itemTmp);
			itemTmp = s_ButtonHookAccess->GetHookedWndItem(GetParent(hWnd));

			if(itemTmp.hWnd != NULL && itemTmp.wpfcaller != NULL)
			{
				// we want to obtain the File Location & File Name & File Types from the Dialogs
				// ~this is the whole point why we hooked into it				
				log(LOGGING_DEBUG, L"Calling GetFileNameAndLocationStringValuesFromDialogWindow()");
				if((GetFileNameAndLocationStringValuesFromDialogWindow(hWnd, itemTmp.wpfcaller)) && 
				   (lstrlen(itemTmp.wpfcaller->m_strFileNameLocationBuf) > 0))
				{
					log(LOGGING_DEBUG, L"About to post WM_CLIENT_EVENT_OPENFILEORSAVEASDIALOGOCCURED to WPFCallerThread");
					log(LOGGING_DEBUG, L"Found FileNameLocation - %s", itemTmp.wpfcaller->m_strFileNameLocationBuf);
					log(LOGGING_DEBUG, L"Found FileTypes - %s", itemTmp.wpfcaller->m_strFileTypesBuf);
					
					// Post to the WPFCallerThread that this Event Occured
					// WM_CLIENT_EVENT_OPENFILEORSAVEASDIALOGOCCURED (WM_USER + 808)
					PostThreadMessage(itemTmp.wpfcaller->m_threadID, (WM_USER + 808),0,0);
				}
				else 
				{
					// This should resolve now since we are looking only for command id 1
					log(LOGGING_LOW, L"FileOpenOrFileSaveDialog() Error - GetFileNameAndLocationStringValuesFromDialogWindow Failed!");
				}
			}
			else
			{
				log(LOGGING_LOW, L"FileOpenOrFileSaveDialog() Error - Couldn't fetch Parents WPFCaller object - skipping file resolution");
			}						
		}

		// Forward all Messages
		result = CallWindowProc(item.DefWndProc, hWnd, Msg, wParam, lParam);
	}

	return result;
}
//////////////////////////////////////////////////////////////////////
// Func: CallWndHookProc()
// Desc: Function that will replace the normal WndProc with the HookWndProc 
//		 procedure. It will do that for every process on the system that 
//		 has a window. It will assign the function HookWndProc()
//       
// Prms:  nCode [in]  = if == HC_ACTION CallWndHookProc must process the message
//		  wParam [in] = specifies if message is send by current process 
//						non-zero if it is current process, null otherwise
//		  LPARAM [in] = pointer to a CWPRETSTRUCT that contains details about the message
//
// Note: see MSDN for CallWndRetProc Function passed in with SetWindowHookEx
//		 ~We don't check wParam (pass directly thru) because we want to process all messages
//		  for all processes and don't care where it originates
//       
// IMP!: In this function we check the Window and decide whether to display the button or not
//		 ~!IMP) WH_CALLWNDPROC hook is called in the context of the thread that calls SendMessage
// 
// Retr: LRESULT, 
//////////////////////////////////////////////////////////////////////
LRESULT WINAPI CallWndHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{

	if (nCode == HC_ACTION)
	{
		LPCWPSTRUCT pCwp = (LPCWPSTRUCT) lParam;							

		// Do we care about this Window?, We don't want to hook into all Windows
		if(IsAWindowToHookInto(pCwp->hwnd)) 
		{
			// Do we care about this Message?
			if((pCwp->message == m_WMHook) /* We'll receive the Hook Message StartButtonHook is called */
			   || (pCwp->message == WM_NCACTIVATE) /* Activates & deactivates a window, caption is redrawn. */
			   || (pCwp->message == WM_CREATE) /* A new window is being created */
			   || (pCwp->message == WM_ACTIVATE) /* a window is being activated */
			   || (pCwp->message == WM_PAINT) /* I added this after figguring out that drag n' droped files don't trigger above messages */
			   || (pCwp->message == WM_NCPAINT) /* Same as for WM_PAINT */
			   )
		    {
				
				// Log that we are hooking into a FileOpen Or FileSaveAsDialog
				if(IsFileOpenOrFileSaveAsDialog(pCwp->hwnd))
				   log(LOGGING_DEBUG, L"FileOpenOrFileSaveDialog() We are hooking into a FileOpen or FileSaveDialog");

				// Create a hook Item, We are ready to hook into this Window
				HookWndItem item;
				
				// hWnd = the handle to the current window
				// DefWndProc = handle of the this window procedure (the address that is replaced by our Hook Function)
				// HookWndProc = the address of this DLL instance's HookWndProc Function
				item.hWnd = pCwp->hwnd;  								
				item.DefWndProc = reinterpret_cast<WNDPROC>(GetWindowLong(pCwp->hwnd,GWL_WNDPROC)); 
				item.HookWndProc = MyWndProc;
				item.wpfcaller = NULL; // The Caption Button will be initialized by MyHookWnd				
				
				// Add the hook Item to the Global List
				s_ButtonHookAccess->InsertHookedWndItem(item);

				////
				// ********************** INSTALL THE NEW WNDHOOK_PROCEDURE **********************
				// *******************************************************************************
				// GWL_WNDPROC - Sets a new address for the window procedure (WinProc())
				// Windows NT/2000/XP: You cannot change this attribute if the window does not 
				// belong to the same process as the calling thread. */The hook is responsible for
				// putting the .dll into the memoryspace of every process. This Dll iterates thru 
				// all the Windows and right here hooks into the WndProc
				////
				log(LOGGING_LOW, L"Installing HookWndProc for Window %u", pCwp->hwnd);

				// We have to call SetWindowLongPtr to install the WNDPROC. Not doins so causes
				// our UnHook message to never be received by the Hooked Wnd
				int result = SetWindowLongPtr( pCwp->hwnd, GWL_WNDPROC, (LONG) MyWndProc );														
			}						
		}	
	}	
	
	// We now pass in our Hook Handle down the current hook chain (in case other hooks are listening)
	return (CallNextHookEx( s_ButtonHookAccess->get_hHook(), nCode, wParam, lParam ));
}
//////////////////////////////////////////////////////////////////////
// Func: Helper_ReadSafeArrayIntoWCharArr()
// Desc: Small Helper function to quickly read a SafeArray Into a WChar Array
//		 
// Prms: +pDes, char destination buffer to copy into
//		 -size, passes out the size of the safearray
//		 +pSrc, the Safearray source to copy from
//
// Note: Not much safety checking going on. Make sure the buffer has enough space!
//		 ~also only 1 dimensional arrays/safearrays work
//
// IMP!: MAX_PATH is the ultimate Max of size and elements of the pDest Buffer
//////////////////////////////////////////////////////////////////////
void Helper_ReadSafeArrayIntoWCharArr(wchar_t pDes[MAX_PATH][MAX_PATH],UINT& size,SAFEARRAY* pSrc)
{
	try
	{
		// this func only handles 1-dim arrays/safearrays
		const int ONE_DIMENSIONAL = 1;

		long lUBound = 0;
		long lLBound = 0;
		SafeArrayGetLBound(pSrc, ONE_DIMENSIONAL, &lLBound); 	
		SafeArrayGetUBound(pSrc, ONE_DIMENSIONAL, &lUBound);	
		
		BSTR* pBSTR;		
		SafeArrayAccessData(pSrc, (void**) &pBSTR);
		for (long n = lLBound; ((n <= lUBound) && (n < MAX_PATH)); ++n)
		{
			_bstr_t bstr(pBSTR[n], true);
			lstrcpyn(&pDes[n][0],(wchar_t*) bstr , MAX_PATH);
		}	
		SafeArrayUnaccessData(pSrc);
		
		// pass out the size
		size = lUBound - lLBound + 1;

		// pSrc was empty so set size to 0
		if((size == 1) && (pDes[0][0] == 0))
			size = 0;
	}
	catch(...)
	{
		return;
	}
}
//////////////////////////////////////////////////////////////////////
// Func: ReadOoganizerSettings()
// Desc: Calls OoganizerSettings COM Object to read in all the settings
//		 into our SharedDS. 
//
//		 These are the important settings we need like Logging details,
//		 What windows to hook into, what window classes, and even 
//		 window layout as well as debugging options
//
// Retr: true, if settings were read, false otherwise
//////////////////////////////////////////////////////////////////////
bool ReadOoganizerSettings()
{
	try
	{	
		IOoganizerSettingsPtr pSettings(__uuidof(OoganizerSettingsAcc));

		// Read Configuration File
		_configurationPtr config = NULL;
		if(FAILED(pSettings->ReadConfigXMLFile(&config)))
			return false;

		// Get ButtonHook Settings
		_ButtonHookSettingsPtr buttonhookSettings = NULL;
		if(FAILED(config->get_ButtonHook(&buttonhookSettings)))
			return false;

		// Let's do that First! ~Imp! Get/Set LogPath & Debug Settings
		_bstr_t LogPath;
		_LoggingSettingsPtr logSettings;
		buttonhookSettings->get_LogSettings(&logSettings);
		logSettings->get_LogPath(LogPath.GetAddress());
		s_ButtonHookAccess->SetLogPath((wchar_t*) LogPath);
		log(LOGGING_DEBUG, L"Setting Log Path: %s", (wchar_t*) LogPath);

		// Logging Detail
		unsigned long nLoggingDetail = 0;
		logSettings->get_LoggingDetail(&nLoggingDetail);		
		s_ButtonHookAccess->SetLoggingDetail(static_cast<LoggingDetail>(nLoggingDetail));
#ifdef _DEBUG
		log(LOGGING_DEBUG, L"Setting Log Detail: 4 (DEBUG)", nLoggingDetail);
#else
		log(LOGGING_DEBUG, L"Setting Log Detail: %u", nLoggingDetail);
#endif

		////
		// Now set this instance's Logging option
		////
		SetLoggingDetail(s_ButtonHookAccess->GetLoggingDetail(),s_ButtonHookAccess->GetLogPath());

		SAFEARRAY* safeArray;

		// Get AllowedProcessNames
		_AllowedProcessNamesWPtr processNames;
		buttonhookSettings->get_AllowedProcessNames(&processNames);
		processNames->get_ProcessName(&safeArray);
		Helper_ReadSafeArrayIntoWCharArr(s_ButtonHookAccess->ALLOWED_PROCESS_NAMES,s_ButtonHookAccess->NUMBER_OF_ALLOWED_PROCESS_NAMES,safeArray);
		
		// log values
		if(s_ButtonHookAccess->NUMBER_OF_ALLOWED_PROCESS_NAMES)
		{
			for (UINT i = 0; i < s_ButtonHookAccess->NUMBER_OF_ALLOWED_PROCESS_NAMES; ++i)
				 log(LOGGING_DEBUG, L"AllowedProcessName: %s", s_ButtonHookAccess->ALLOWED_PROCESS_NAMES[i]);
		}

		// Get AllowedWindowTitles
		_AllowedWindowTitlesWPtr windowTitles;	
		buttonhookSettings->get_AllowedWindowTitles(&windowTitles);
		windowTitles->get_WindowTitle(&safeArray);
		Helper_ReadSafeArrayIntoWCharArr(s_ButtonHookAccess->ALLOWED_WINDOW_TITLES,s_ButtonHookAccess->NUMBER_OF_ALLOWED_WINDOW_TITLES,safeArray);

		// log values
		if(s_ButtonHookAccess->NUMBER_OF_ALLOWED_WINDOW_TITLES)
		{
			for (UINT i = 0; i < s_ButtonHookAccess->NUMBER_OF_ALLOWED_WINDOW_TITLES; ++i)
				 log(LOGGING_DEBUG, L"AllowedWindowTitle: %s", s_ButtonHookAccess->ALLOWED_WINDOW_TITLES[i]);
		}

		// Get AllowedWindowClasses
		_AllowedWindowClassesWPtr windowClasses;	
		buttonhookSettings->get_AllowedWindowClasses(&windowClasses);
		windowClasses->get_WindowClass(&safeArray);		
		Helper_ReadSafeArrayIntoWCharArr(s_ButtonHookAccess->ALLOWED_WINDOW_CLASSES,s_ButtonHookAccess->NUMBER_OF_ALLOWED_WINDOW_CLASSES,safeArray);

		// log values
		if(s_ButtonHookAccess->NUMBER_OF_ALLOWED_WINDOW_CLASSES)
		{
			for (UINT i = 0; i < s_ButtonHookAccess->NUMBER_OF_ALLOWED_WINDOW_CLASSES; ++i)
				 log(LOGGING_DEBUG, L"AllowedWindowClass: %s", s_ButtonHookAccess->ALLOWED_WINDOW_CLASSES[i]);
		}
		return true;
	}
	catch(...)
	{
		return false;
	}
}
//////////////////////////////////////////////////////////////////////
// Func: SystemMetricsTesterHELPER()
// Desc: Use this for Debugging only when you need to know certain system metrics values
//////////////////////////////////////////////////////////////////////
void SystemMetricsTesterHELPER()
{
	int k = 0;
	k = GetSystemMetrics(SM_CXEDGE); //2 on vista
	k = GetSystemMetrics(SM_CYEDGE); //2 on vista

	k = GetSystemMetrics(SM_CXFRAME); //8 on vista
	k = GetSystemMetrics(SM_CYFRAME); //8 on vista

	k = GetSystemMetrics(SM_CXFIXEDFRAME); //3 on vista
	k = GetSystemMetrics(SM_CYFIXEDFRAME); //3 on vista

	k = GetSystemMetrics(SM_CXMIN); //124 on vista
	k = GetSystemMetrics(SM_CYMIN); //36 on vista

	k = GetSystemMetrics(SM_CXBORDER); // 1 on vista
	k = GetSystemMetrics(SM_CYBORDER); // 1 on vista

	k = GetSystemMetrics(SM_CXSIZE); //32 on vista
	k = GetSystemMetrics(SM_CYSIZE); //19 on vista

	k = GetSystemMetrics(SM_CYCAPTION); //20 on vista
	//k = GetSystemMetrics(SM_CXCAPTION);

	k = GetSystemMetrics(SM_CYSMCAPTION); //18 on vista
	//k = GetSystemMetrics(SM_CXSMCAPTION);

	k = GetSystemMetrics(SM_CYSMSIZE); //17 on vista
	k = GetSystemMetrics(SM_CXSMSIZE); //17 on vista
}
//////////////////////////////////////////////////////////////////////
// Func: StartWinProcButtonHook()
// Desc: Exported DLL function to allow a process to start/attach 
//		 the system WinProcButtonhook. This function will attach the WinProc function 
//		 to the process's window via the CallWndHookProc() function
//
// Note: We'll check if the system is supported here. We'll only allow
//		 to start the hook, if that is the case
//       
// Retr: TRUE, if succesfully attached, FALSE otherwise (i.e. OS NOT SUPPORTED)
//////////////////////////////////////////////////////////////////////
BOOL WINAPI StartButtonHook()	
{
	// Debugging ONLY when we need certain values (comment/uncomment)
	//SystemMetricsTesterHELPER();
	
	// Let's try to see if we can force a rehook
	bool bThereWasAnExistingHook = false;
	if(s_ButtonHookAccess->get_hHook() != NULL)
	{		
		s_ButtonHookAccess->set_hHook(NULL);
		bThereWasAnExistingHook = true;
	}

	// Global doesn't exist, we can start
	if(s_ButtonHookAccess->get_hHook() == NULL)
	{
		// Delete All Shared Data Hook Data - Just in case previous DLL Instance corrupted HookData
		s_ButtonHookAccess->ClearHookSData();

		// Don't start ButtonHook unless the OS supports it
		VARIANT_BOOL bIsSupported;
		_EnvironmentCCWPtr env(__uuidof(EnvironmentCCW));		
		env->IsWindowsOSSupported(&bIsSupported);				

		if(bIsSupported == -1) // TRUE
		{	
			bool bSettings = ReadOoganizerSettings();
			clearLog(); // Start Fresh (with a new Log)

			// We won't be able to log until we have read in the settings			
			if(bThereWasAnExistingHook)
				log(LOGGING_LOW, L"A previous ButtonHook Existed - overwriting it");

			if(!bSettings) 			
				log(LOGGING_LOW, L"ReadOoganizerSettings Failed!");
			else			
				log(LOGGING_MEDIUM, L"ReadOoganizerSettings Succeeded");

			// There really is no sense to start ButtonHook with no Settings Info
			if(bSettings)
			{
				log(LOGGING_LOW, L"StartButtonHook Called()");
				
				// Create/Store Global Hook (every instance needs access to this HOOK)
				// ~WH_CALLWNDPROC hook is called in the context of the thread that calls SendMessage
				log(LOGGING_DEBUG, L"Setting the Windows Hook");
				HHOOK hook = SetWindowsHookEx( WH_CALLWNDPROC, CallWndHookProc, s_hModule, 0 );
				
				if(hook == NULL)
				{
					log(LOGGING_LOW, L"Windows Hook Failed error %i", GetLastError());
					return FALSE;
				}
				else
				{
					// Assign the hook value to the Global DS
					log(LOGGING_DEBUG, L"Windows Hook Succeeded Hook Set %i", hook);
					s_ButtonHookAccess->set_hHook(hook);
				}

				// Send Hook Msg - This forces all windows already open in the system and that
				// we want to hook into to load the captionbutton

				if(m_WMHook >= 0xC000 && m_WMHook <= 0xFFFF)
				{
					log(LOGGING_DEBUG, L"StartButtonHook - About to call EnumWindows");
					EnumWindows( EnumProc, _MSG_HOOK );	
					log(LOGGING_LOW, L"StartButtonHook EnumWindows Returns - Everything is O.K.");
				}
				else
				{
					log(LOGGING_LOW, L"Registered Message m_WMHook is invalid - something is terribly wrong");
					return FALSE;
				}

				return TRUE;
			}
		}
	}	
	return FALSE;
}
//////////////////////////////////////////////////////////////////////
// Func: StopWinProcButtonHook()
// Desc: Exported DLL function to allow a process to stop/deattach 
//		 the WinProcButton system hook
//       
// Retr: TRUE, if succesfully deattached, FALSE otherwise
//////////////////////////////////////////////////////////////////////
BOOL WINAPI StopButtonHook()
{
	// Global does exist, we can exit
	if(s_ButtonHookAccess->get_hHook() != NULL)
	{	
		// Send UnHook Msg
		if(m_WMUnHook >= 0xC000 && m_WMUnHook <= 0xFFFF)
		{
			log(LOGGING_LOW, L"StopButtonHook Called()");		
			EnumWindows( EnumProc, _MSG_UNHOOK );	
			log(LOGGING_LOW, L"StopButtonHook() EnumWindows Returns - Everything is O.K.");
		}
		else
		{
			log(LOGGING_LOW, L"Registered Message m_WMUnHook is invalid - something is terribly wrong");
			return FALSE;
		}

		try
		{
			// now we can exit
			if ( UnhookWindowsHookEx( s_ButtonHookAccess->get_hHook() ) != 0 )
			{
				log(LOGGING_MEDIUM, L"Successfully Unhooked Hook %i", s_ButtonHookAccess->get_hHook());
			}
			else
			{
				log(LOGGING_LOW, L"Error Occrured in StopButtonHook %i", GetLastError());
				return FALSE;
			}
		
			// Reset Global Hook - Delete All Shared Data
			log(LOGGING_HIGH, L"Reset Global Hook & Clearing ALL Hook Related Data");
			s_ButtonHookAccess->set_hHook(NULL);
			s_ButtonHookAccess->ClearHookSData();

			// New Code: ~errors out~ Unregister all Window Classes here (used to be done in ~WPFCaller())
			/*if(!UnregisterClass(L"W32ButtonHookWndClass", s_hModule))
			   log(LOGGING_DEBUG, L"Error occured for UnregisterClass W32ButtonHookWndClass");*/
		}
		catch(...)
		{
			log(LOGGING_LOW, L"Error occured in StopButtonHook");
			return FALSE;
		}
		return TRUE;
	}
	return FALSE;	
}
//////////////////////////////////////////////////////////////////////
// Func: IsButtonHookSet()
// Desc: Exported DLL function to allow a caller to know if the buttonhook
//		 is set or not
//
// Retr: TRUE, if Hook is attached, FALSE otherwise
//////////////////////////////////////////////////////////////////////
BOOL WINAPI IsButtonHookSet()
{
	return (s_ButtonHookAccess->get_hHook() != NULL);
}
//////////////////////////////////////////////////////////////////////
// Func: GetAllHookedWindowHandles()
// Desc: Exported DLL function to allow a caller to retrieve all Hooked
//		 Window Handles.
//		 ~passed in pBuf must be at least of size MAX_PATH
// 
// Retr: int, returns the size of the passed out pBuf list
//////////////////////////////////////////////////////////////////////
int WINAPI GetAllHookedWindowHandles(int* pBuf)
{
	if(s_ButtonHookAccess->get_hHook() != NULL)
	{
		int buf[MAX_PATH] = {0};
		int nsize = 0;
		s_ButtonHookAccess->GetHookedWindowListNSize(buf,&nsize);

		////
		// there could be nulls in the buf, so filter them out,
		// to return a complete list the caller. Copy only Non-Null
		// values to the output buffer * and ONLY EXISTING WINDOWS *
		////
		int nNullsOrInvalid = 0;
		for(int i = 0; i < nsize; ++i)
		{
			if(buf[i] != NULL && ::IsWindow((HWND) buf[i]))
			   *(pBuf + i) = buf[i];
			else
				++nNullsOrInvalid;
		}
		
		// return the adjusted size, if needed
		log(LOGGING_DEBUG, L"GetAllHookedWindowHandles() got called returning WindowHandles Buf size of %u", (nsize - nNullsOrInvalid));
		return (nsize - nNullsOrInvalid);
	}
	return 0;
}
//////////////////////////////////////////////////////////////////////
// Func: DllMain()
// Desc: Dll Main Entry Point - The DLL is being attached to every
//		 process in the system! ~therefore it must be fast. Also
//		 we can't start the main register hook function from here
//       system call from here. (because this dll get loaded many times,
//		 we only however want the system hook code to be called only once.
//       
// Note: Attaching the dll will cause MSG_HOOK to be registred
//
// Retr: TRUE, always
//////////////////////////////////////////////////////////////////////
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{	

    switch ( ul_reason_for_call )
	{
		case DLL_PROCESS_ATTACH:												
			
			// This is to access DS segment's Hook Information,
			// Across all instances of this DLL.
			s_ButtonHookAccess = new CWHPaccess();
					
			// Store local Module Handle
			s_hModule = (HINSTANCE) hModule;
			
			// message is used to send to all windows when the HOOK attaches
			m_WMHook = RegisterWindowMessage( MSG_HOOK );						

			// message is used to send to all windows when the HOOK unattaches			
			m_WMUnHook = RegisterWindowMessage( MSG_UNHOOK );			

			// get the shared setting logging information and store it into this instance
			SetLoggingDetail(s_ButtonHookAccess->GetLoggingDetail(), s_ButtonHookAccess->GetLogPath());

			break;
		case DLL_PROCESS_DETACH:		
		case DLL_THREAD_DETACH:
		case DLL_THREAD_ATTACH:						
		default:
			break;
    }

    return TRUE;
}