// This is the main DLL file. #include "stdafx.h" #define DLLAPI_BUTTON_HOOK #include "ButtonHook.h" #include 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(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(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; }