Files
Oogynize/Hooks/ButtonHook/ButtonHook.cpp

1153 lines
40 KiB
C++

// 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;
}