#include "StdAfx.h" #include "WPFCaller.h" #include "CWHPaccess.h" #include "shellapi.h" #include "dde.h" #include "ddeml.h" using namespace Ooganizer; // Specifies a nonzero timer identifier #define NID_RESTORE_EVENT_TIMER (WM_USER + 1) #define NID_CLOSE_BUT_NOT_DESTROY_EVENT_TIMER (WM_USER + 2) // * Messages send from WPFCaller class to WPFProc Thread #define WM_KILLTHREAD (WM_USER + 501) // * Messages Send from W32Button to WPFProc Thread #define WM_GETBUTTONTHEMESETTING (WM_USER + 601) #define WM_W32BUTTONCLICKED (WM_USER + 602) // * External Messages * Send to us by outside // Programs (most likely WPFForm or Platform) #define WM_W32PINGISALIVE (WM_USER + 700) #define WM_W32SETBUTTONSTATE (WM_USER + 701) #define WM_W32GETBUTTONSTATE (WM_USER + 702) #define WM_W32SETPARENTWINDOWTRANSPERANCY (WM_USER + 703) #define WM_W32SETPARENTWINDOWACTIVE (WM_USER + 704) #define WM_W32POSTMESSAGETOPARENTWINDOW (WM_USER + 705) // * Messages Only handled by WPFThreadProc * Events Send to // C# Client Event Dispatcher for handling #define WM_CLIENT_EVENT_WINDOWACTIVATE (WM_USER + 800) #define WM_CLIENT_EVENT_DRAGNDROPOCCURED (WM_USER + 801) #define WM_CLIENT_EVENT_MDICHILDWINDOWSWITCHOCCURED (WM_USER + 802) #define WM_CLIENT_EVENT_PARENTGETSETTEXTOCCURED (WM_USER + 803) #define WM_CLIENT_EVENT_PARENTNOTIFYLBUTTONCLICKOCCURED (WM_USER + 804) #define WM_CLIENT_EVENT_PARENTNOTIFYWMCREATEOCCURED (WM_USER + 805) #define WM_CLIENT_EVENT_PARENTCLOSEWITHOUTDESTROYOCCURED (WM_USER + 806) #define WM_CLIENT_EVENT_PARENTSETFOCUSOCCURED (WM_USER + 807) #define WM_CLIENT_EVENT_OPENFILEORSAVEASDIALOGOCCURED (WM_USER + 808) #ifdef _DEBUG #import "..\\..\\Target\\Debug\\Settings.tlb" raw_interfaces_only #else #import "..\\..\\Target\\Release\\Settings.tlb" raw_interfaces_only #endif using namespace Settings; // access the SharedDS *initialized by WPFCaller() constructor* CWHPaccess* s_WPFCallerAccess = NULL; ////////////////////////////////////////////////////////////////////// // Func: ReadButtonThemeSetting // Desc: static function to quickly read in Button Theme Settings. // ~Uses COM, must be called ONLY from within a COM initialized Thread! ////////////////////////////////////////////////////////////////////// bool ReadButtonThemeSetting(WPFCaller* pCaller) { HRESULT hr = S_FALSE; _ButtonThemeNIconSettingsCCWPtr pButtonThemeNIconSetting; hr = pButtonThemeNIconSetting.CreateInstance(__uuidof(ButtonThemeNIconSettingsCCW)); if(SUCCEEDED(hr)) { log(LOGGING_HIGH, L"Succeed creating ButtonThemeNIconSettings Instance"); } else { log(LOGGING_LOW, L"FAILED to create the ButtonThemeNIconSettings COM Instance"); return false; } //// // Get the Button Theme N Icon Settings //// IButtonThemeSettingPtr pThemeSetting; IButtonIconLocationPtr pIconLocation; pButtonThemeNIconSetting->GetIButtonThemeInterface(&pThemeSetting); pButtonThemeNIconSetting->GetIButtonIconLocation(&pIconLocation); if(pThemeSetting == NULL) log(LOGGING_LOW, L"pThemeSetting == NULL - Error"); if(pIconLocation == NULL) log(LOGGING_LOW, L"pIconLocation == NULL - Error"); if((pThemeSetting == NULL) || (pIconLocation == NULL)) return false; //// // Get Button Dimension/Location //// if(SUCCEEDED(hr)) hr = pThemeSetting->Top(&pCaller->m_ButtonThemeSetting.Top); if(SUCCEEDED(hr)) hr = pThemeSetting->Right(&pCaller->m_ButtonThemeSetting.Right); if(SUCCEEDED(hr)) hr = pThemeSetting->Height(&pCaller->m_ButtonThemeSetting.Height); if(SUCCEEDED(hr)) hr = pThemeSetting->Width(&pCaller->m_ButtonThemeSetting.Width); //// // Get Button Icon Location //// if(SUCCEEDED(hr)) pIconLocation->GetBUTTON_UP(pCaller->m_ButtonThemeSetting.bstrBUTTON_UP.GetAddress()); if(SUCCEEDED(hr)) pIconLocation->GetBUTTON_DOWN(pCaller->m_ButtonThemeSetting.bstrBUTTON_DOWN.GetAddress()); if(SUCCEEDED(hr)) pIconLocation->GetTOGGLE_UP(pCaller->m_ButtonThemeSetting.bstrTOGGLE_UP.GetAddress()); if(SUCCEEDED(hr)) { log(LOGGING_HIGH, L"ReadButtonTheme Settings Successfully - Height=%i, Width=%i, Top=%i, Right=%i", pCaller->m_ButtonThemeSetting.Height,pCaller->m_ButtonThemeSetting.Width,pCaller->m_ButtonThemeSetting.Top, pCaller->m_ButtonThemeSetting.Right); if(pCaller->m_ButtonThemeSetting.bIsInitialized) pCaller->m_ButtonThemeSetting.bIsChanged = true; if(!pCaller->m_ButtonThemeSetting.bIsInitialized) pCaller->m_ButtonThemeSetting.bIsInitialized = true; } else { pCaller->m_ButtonThemeSetting.bIsChanged = false; pCaller->m_ButtonThemeSetting.bIsInitialized = false; log(LOGGING_LOW, L"ReadButtonTheme Settings FAILED!"); } if(SUCCEEDED(hr)) return true; else return false; } //// // When we want to communicate back to the ButtonHook from externaly windows, // we use these custom messages in order to do so. We keep track of them seperatly // because we allow Custom Messages to return a Value to the caller. //// UINT s_nMessagesCustomMessages = 6; UINT s_nMessagesCustomMessagesWeHave[] = { WM_W32PINGISALIVE, WM_W32SETBUTTONSTATE, WM_W32GETBUTTONSTATE, WM_W32SETPARENTWINDOWTRANSPERANCY, WM_W32SETPARENTWINDOWACTIVE, WM_W32POSTMESSAGETOPARENTWINDOW }; bool g_IsMessageACustomMessage(UINT msgID) { for(UINT i = 0; i < s_nMessagesCustomMessages; ++i) { if(s_nMessagesCustomMessagesWeHave[i] == msgID) return true; } return false; } //// // For wndproc to know what we care about and pass it down to to W32WndProc // ~these messages will be tagged with WM_USER for us to handle in our W32WndProc //// UINT s_nMessagesWPFormCaresAbout = 2; UINT s_MessagesWPFormCaresAbout[] = { WM_ACTIVATE, WM_SIZE, }; bool g_IsMessageWPFormCaresAbout(UINT msgID) { for(UINT i = 0; i < s_nMessagesWPFormCaresAbout; ++i) { if(s_MessagesWPFormCaresAbout[i] == msgID) return true; } return false; } //// // For wndproc to know what we care about and pass it down to to MainWndProc // ~these messages will be send straight down for us to handle in our MainWndProc //// UINT s_nMessagesW32WindowCaresAbout = 8; UINT s_MessagesW32WindowCaresAbout[] = { WM_PAINT, WM_DISPLAYCHANGE, WM_THEMECHANGED, WM_SIZE, WM_MOVE, WM_LBUTTONDOWN, WM_MOUSEMOVE, WM_SHOWWINDOW }; bool g_IsMessageW32WindowCaresAbout(UINT msgID) { for(UINT i = 0; i < s_nMessagesW32WindowCaresAbout; ++i) { if(s_MessagesW32WindowCaresAbout[i] == msgID) return true; } return false; } ////////////////////////////////////////////////////////////////////// // Desc: We need to make sure that we have the init button settings, // before we can continue in many of our Messaging functions ////////////////////////////////////////////////////////////////////// void MakeSureButtonThemeSettingWereInitialized(WPFCaller* pCaller) { // We must make sure the Button has all the settings before // we can draw,move it, etc so sleep the thread until we know we have them while(!pCaller->m_ButtonThemeSetting.bIsInitialized) Sleep(20); } ////////////////////////////////////////////////////////////////////// // Desc: For our WPFThread callback into WPForm we must make sure // that we have the handle for our W32Button so to pass it down to them ////////////////////////////////////////////////////////////////////// void MakeSureW32ButtonWindowGotCreated(WPFCaller* pCaller) { // We must make sure the Button window got created so we can pass the // handle down while(pCaller->m_hwndW32Window == NULL) Sleep(20); } ////////////////////////////////////////////////////////////////////// // Desc: For our Win32 Thread we must make sure we have a valid pointer to // a WPFCaller Object. ////////////////////////////////////////////////////////////////////// WPFCaller* GetWPFCallerObject(HWND ParenthWnd) { if(s_WPFCallerAccess) { BLANK_HookWndItem(Item); Item = s_WPFCallerAccess->GetHookedWndItem(ParenthWnd); WPFCaller* pCaller = Item.wpfcaller; return pCaller; } return NULL; } ////////////////////////////////////////////////////////////////////// // Func: DrawIconButton (Owner Drawing out Buttons) // Desc: Responsible for drawing the owner buttons onto the caption bar ////////////////////////////////////////////////////////////////////// bool DrawIconButton(HINSTANCE hInstance,HDC hdc, WPFCaller* pCaller) { // static Icon handles: static HANDLE hCurrIcon = NULL; static HANDLE hButtonUp = NULL; static HANDLE hButtonDown = NULL; static HANDLE hToggleUp = NULL; // LoadIcon only loads once, but LoadImage does not, // so in case you call the latter, use this flag static bool bIconsLoaded = false; // Make sure that we don't continue unless we have all the settings MakeSureButtonThemeSettingWereInitialized(pCaller); if (!bIconsLoaded || pCaller->m_ButtonThemeSetting.bIsChanged) { //// // ButtonUp //// if(!hButtonUp) { HANDLE hTemp = hButtonUp; hButtonUp = NULL; //*avoid poss. threading issue DestroyIcon((HICON)hTemp); } hButtonUp = LoadImage(hInstance, (WCHAR*)pCaller->m_ButtonThemeSetting.bstrBUTTON_UP,IMAGE_ICON,pCaller->m_ButtonThemeSetting.Width,pCaller->m_ButtonThemeSetting.Height,LR_LOADFROMFILE); if(!hButtonUp) log(LOGGING_LOW, L"hButtonUp LoadImage Failed! with error %i - path %s",GetLastError(),(wchar_t*)pCaller->m_ButtonThemeSetting.bstrBUTTON_UP); //// // ButtonDown //// if(!hButtonDown) { HANDLE hTemp = hButtonDown; hButtonDown = NULL; //*avoid poss. threading issue DestroyIcon((HICON)hTemp); } hButtonDown = LoadImage(hInstance, (WCHAR*)pCaller->m_ButtonThemeSetting.bstrBUTTON_DOWN,IMAGE_ICON,pCaller->m_ButtonThemeSetting.Width,pCaller->m_ButtonThemeSetting.Height,LR_LOADFROMFILE); if(!hButtonDown) log(LOGGING_LOW, L"hButtonDown LoadImage Failed! with error %i - path %s",GetLastError(),(wchar_t*)pCaller->m_ButtonThemeSetting.bstrBUTTON_DOWN); //// // ToogleUp //// if(!hToggleUp) { HANDLE hTemp = hToggleUp; hToggleUp = NULL; //*avoid poss. threading issue DestroyIcon((HICON)hTemp); } hToggleUp = LoadImage(hInstance, (WCHAR*)pCaller->m_ButtonThemeSetting.bstrTOGGLE_UP,IMAGE_ICON,pCaller->m_ButtonThemeSetting.Width,pCaller->m_ButtonThemeSetting.Height,LR_LOADFROMFILE); if(!hToggleUp) log(LOGGING_LOW, L"hToggleUp LoadImage Failed! with error %i - path %s",GetLastError(),(wchar_t*)pCaller->m_ButtonThemeSetting.bstrTOGGLE_UP); // Everything went well. bIconsLoaded = true; pCaller->m_ButtonThemeSetting.bIsChanged = false; } //// // Now Draw the proper Icon State //// bool bUseToogleState = (pCaller->m_ButtonToggledState != BUTTON_NONE); if(!bUseToogleState && pCaller->m_ButtonState == BUTTON_DOWN) hCurrIcon = hButtonDown; else if(pCaller->m_ButtonState == BUTTON_UP) hCurrIcon = hButtonUp; else if(bUseToogleState || pCaller->m_ButtonState == BUTTON_TOGGLED) hCurrIcon = hToggleUp; if(hCurrIcon != NULL) { if (!DrawIconEx(hdc,0,0,(HICON) hCurrIcon, pCaller->m_ButtonThemeSetting.Width, pCaller->m_ButtonThemeSetting.Height, 0, NULL, DI_NORMAL)) { log(LOGGING_LOW, L"DrawIconEx Failed!"); return false; } } return true; } ////////////////////////////////////////////////////////////////////// // Desc: Set static HeightNWidth (keeping track of the Window size) ////////////////////////////////////////////////////////////////////// void Helper_GetRectAndSetHeightNWidth(HWND hWnd, WPFCaller* pCaller) { RECT rect; GetWindowRect(hWnd,&rect); pCaller->m_wndHeight = (rect.bottom - rect.top); pCaller->m_wndWidth = (rect.right - rect.left); pCaller->m_wndRECT = rect; } ////////////////////////////////////////////////////////////////////// // Desc: Set static HeightNWidth (keeping track of the Window size) ////////////////////////////////////////////////////////////////////// void Helper_GetRectAndSetHeightNWidthUsingPlacement(HWND hWnd, WPFCaller* pCaller) { WINDOWPLACEMENT place; place.length = sizeof(WINDOWPLACEMENT); GetWindowPlacement(hWnd,&place); pCaller->m_wndHeight = (place.rcNormalPosition.bottom - place.rcNormalPosition.top); pCaller->m_wndWidth = (place.rcNormalPosition.right - place.rcNormalPosition.left); pCaller->m_wndRECT = place.rcNormalPosition; } ////////////////////////////////////////////////////////////////////// // Func: W32WndProc (W32Window) // Desc: Responsible for handling the Wnd32Window's incoming messages ////////////////////////////////////////////////////////////////////// LRESULT CALLBACK W32WndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; PAINTSTRUCT ps; HDC hdc; int nTop = 0; int nRight = 0; bool bIsMaximized = false; HINSTANCE hInst = NULL; // Use the ParentHWND to get access to the correct WPFCaller instance, // we need to do this in case multiple windows exist in the same process WPFCaller* pCaller = GetWPFCallerObject(::GetParent(hWnd)); // Nothing to do here, WPFCaller is not ready (just handle the message) if(pCaller == NULL) goto HANDLE_MESSAGE; //// // Messages should be handled here (if we are interested) //// try { // For some calls we need th HINSTANCE hInst = pCaller->m_hInst; switch(Msg) { //// // Win32Button Messages we handle //// case WM_SIZE: if(wParam == SIZE_MINIMIZED) { pCaller->m_bButtonIsMinimized = true; } else if((wParam == SIZE_RESTORED && pCaller->m_bButtonIsMinimized) || (wParam == SIZE_MAXIMIZED && pCaller->m_bButtonIsMinimized)) { // the Button receives this message way before the parent does. pCaller->m_bButtonIsBeingRestored = true; } // Intentional Fallthru case WM_DISPLAYCHANGE: case WM_MOVE: { // ~Calculate Offset for Maximized Windows - IMP // in vista, we need 7 height offset // we need 5 width offset // ~i tried using the best values that make sense const int YHEIGHT_OFFSET = GetSystemMetrics(SM_CYFRAME) - 1; // subtract 1 to better position the button (and not cut it off as much) const int XWIDTH_OFFSET = (2 * GetSystemMetrics(SM_CXEDGE)) + GetSystemMetrics(SM_CXBORDER); bool bIsMaximized = (IsZoomed(pCaller->m_hWnd) == TRUE); int YOFFSET = 0; int XOFFSET = 0; if(bIsMaximized) { YOFFSET = YHEIGHT_OFFSET; XOFFSET = XWIDTH_OFFSET; } // Set the Window Position (Use Offset if need be) MakeSureButtonThemeSettingWereInitialized(pCaller); Helper_GetRectAndSetHeightNWidth(pCaller->m_hWnd, pCaller); nTop = pCaller->m_wndRECT.top + pCaller->m_ButtonThemeSetting.Top; nRight = pCaller->m_wndRECT.left + pCaller->m_wndWidth - pCaller->m_ButtonThemeSetting.Right; SetWindowPos(hWnd, HWND_TOP, nRight - XOFFSET, nTop + YOFFSET, pCaller->m_ButtonThemeSetting.Width, pCaller->m_ButtonThemeSetting.Height, NULL); bool bIsMinimizeMoveMessage = false; if(Msg == WM_MOVE) { int x = LOWORD(lParam); int y = HIWORD(lParam); bIsMinimizeMoveMessage = (x > 33500) && (y > 33500); } // If the window is just too damn small to hold all the buttons just hide it! // ~better user experience const int MIN_WITDH_TO_DISPLAY_BUTTON_IN = 300; if((pCaller->m_wndWidth > 25) && /* make sure width is bigger >0 may as well put in some magic number buffer */ !bIsMinimizeMoveMessage && (pCaller->m_wndWidth < MIN_WITDH_TO_DISPLAY_BUTTON_IN) && !pCaller->m_bButtonIsArtificiallyHidden_MOVE && !pCaller->m_bButtonIsMinimized) { log(LOGGING_DEBUG, L"WM_MOVE Setting ButtonWindow Artificially Hidden to true"); pCaller->m_bButtonIsArtificiallyHidden_MOVE = true; RedrawWindow(pCaller->m_hwndW32Window,NULL,NULL,(RDW_INVALIDATE | RDW_UPDATENOW)); } else if((pCaller->m_wndWidth >= MIN_WITDH_TO_DISPLAY_BUTTON_IN) && pCaller->m_bButtonIsArtificiallyHidden_MOVE && !pCaller->m_bButtonIsMinimized) { log(LOGGING_DEBUG, L"WM_MOVE Setting ButtonWindow Artificially Hidden to false"); pCaller->m_bButtonIsArtificiallyHidden_MOVE = false; pCaller->StartRestoreEventFadeTimer(); } return 0; } break; case WM_SHOWWINDOW: if((BOOL) wParam == FALSE) // window is being hidden { pCaller->SetAlpha(0); } else if((BOOL) wParam == TRUE) // window is being shown { pCaller->StartRestoreEventFadeTimer(); } return 0; case WM_MOUSEMOVE: TRACKMOUSEEVENT mouseEvent; mouseEvent.cbSize = sizeof(TRACKMOUSEEVENT); mouseEvent.dwFlags = TME_HOVER | TME_LEAVE; mouseEvent.hwndTrack = hWnd; mouseEvent.dwHoverTime = 25; TrackMouseEvent(&mouseEvent); break; case WM_LBUTTONDOWN: log(LOGGING_HIGH, L"ButtonWindow received ButtonClick Sending Click Event to WPForm"); PostThreadMessage(pCaller->m_threadID,WM_W32BUTTONCLICKED,NULL,NULL); return 0; case WM_MOUSEHOVER: pCaller->m_ButtonState = BUTTON_UP; RedrawWindow(pCaller->m_hwndW32Window,NULL,NULL,(RDW_INVALIDATE | RDW_UPDATENOW)); return 0; case WM_MOUSELEAVE: pCaller->m_ButtonState = BUTTON_DOWN; RedrawWindow(pCaller->m_hwndW32Window,NULL,NULL,(RDW_INVALIDATE | RDW_UPDATENOW)); return 0; case WM_TIMER: // FadeInTimer (Restore from Minimize) if(wParam == NID_RESTORE_EVENT_TIMER) { // Increment Opaqueness const int SLEEP_INCREMENT = 1; // 1 == lowest const int LOWER_INCREMENT = 7; const int MIDDLE_INCREMENT = 15; const int UPPER_INCREMENT = 25; // Increment Points const int SLEEP_DONE_POINT = 7; // (7 + 1) * 20 = 160ms of sleep const int LOWER_POINT = 60; const int MIDDLE_POINT = 160; const int UPPER_POINT = 230; BYTE alpha = pCaller->GetAlpha(); if(alpha >= UPPER_POINT) alpha = 255; // just show the thing already else if(alpha <= SLEEP_DONE_POINT) // sleep a little in the beginning... alpha += SLEEP_INCREMENT; else if(alpha <= LOWER_POINT) alpha += LOWER_INCREMENT; else if(alpha <= MIDDLE_POINT) alpha += MIDDLE_INCREMENT; else alpha += UPPER_INCREMENT; // Set the calculated Alpha Opaqueness pCaller->SetAlpha(alpha); // Close Restore Timer if we are done if(pCaller->GetAlpha() == 255) { // Kill the Restore Timer KillTimer(hWnd,NID_RESTORE_EVENT_TIMER); log(LOGGING_DEBUG, L"Stopping Restore Event Timer"); } } else if(wParam == NID_CLOSE_BUT_NOT_DESTROY_EVENT_TIMER) // Closing Timer for MDI Apps (notify WPFCaller) { // We'll run this timer 21x, that should give us ~5 seconds (more than enough, i hope in 99% of cases) if(pCaller->m_StartCloseButNotDestroyTimerCount == 20) { log(LOGGING_DEBUG, L"Stopping Close But not Destroy Timer"); pCaller->m_StartCloseButNotDestroyTimerCount = 0; KillTimer(hWnd,NID_CLOSE_BUT_NOT_DESTROY_EVENT_TIMER); } else { log(LOGGING_DEBUG, L"EventDispatcherHandler - WM_CLOSE but no WM_DESTROY Occured letting WPFCaller know"); pCaller->m_StartCloseButNotDestroyTimerCount = pCaller->m_StartCloseButNotDestroyTimerCount + 1; PostThreadMessage(pCaller->m_threadID, WM_CLIENT_EVENT_PARENTCLOSEWITHOUTDESTROYOCCURED,0,0); } } break; case WM_PAINT: // If we are minimized and we are getting messages from our parent // ~now we can launch the restore Timer if(pCaller->m_bButtonIsBeingRestored && (wParam == (WPARAM) pCaller->m_hWnd) && (lParam == (LPARAM) pCaller->m_hWnd)) { // restore timer will from now on handle the restore pCaller->m_bButtonIsBeingRestored = false; pCaller->m_bButtonIsMinimized = false; // Hides the Button Completly log(LOGGING_DEBUG, L"WM_PAINT is starting StartRestoreEventFadeTimer()"); pCaller->StartRestoreEventFadeTimer(); } // Only Draw the button when we want to if(!pCaller->m_bButtonIsMinimized && !pCaller->m_bButtonIsArtificiallyHidden_MOVE) { hdc = BeginPaint(hWnd, &ps); DrawIconButton(hInst,ps.hdc,pCaller); EndPaint(hWnd, &ps); } return 0; case WM_THEMECHANGED: log(LOGGING_MEDIUM, L"Received WM_THEMECHANGED. W32Button is trying to retrieve new Theme Settings via WPFProc"); PostThreadMessage(pCaller->m_threadID,WM_GETBUTTONTHEMESETTING,NULL,NULL); break; case WM_W32PINGISALIVE: return 1; case WM_W32SETBUTTONSTATE: log(LOGGING_DEBUG, L"Received WM_W32SetButtonState Message. Setting Button to %d", (int)lParam); pCaller->m_ButtonState = (BUTTON_STATE) lParam; // Button will be toggled / untoggled when the caller set it if(pCaller->m_ButtonState == BUTTON_TOGGLED) pCaller->m_ButtonToggledState = BUTTON_TOGGLED; else pCaller->m_ButtonToggledState = BUTTON_NONE; RedrawWindow(pCaller->m_hwndW32Window,NULL,NULL,(RDW_INVALIDATE | RDW_UPDATENOW)); return 1; case WM_W32GETBUTTONSTATE: log(LOGGING_DEBUG, L"Received WM_W32GetButtonState Message. Sending Button State %d", (int)pCaller->m_ButtonState); return (LRESULT) pCaller->m_ButtonState; case WM_W32SETPARENTWINDOWTRANSPERANCY: { if((int)lParam == 0) log(LOGGING_DEBUG, L"Received WM_W32SETPARENTWINDOWTRANSPERANCY Message with Alpha Setting of %u - Invisible", (int)lParam); else if((int)lParam == 255) log(LOGGING_DEBUG, L"Received WM_W32SETPARENTWINDOWTRANSPERANCY Message with Alpha Setting of %u - Visible", (int)lParam); long dwStyle = (GetWindowLong(pCaller->m_hWnd,GWL_EXSTYLE) | WS_EX_LAYERED); long retVal = SetWindowLong(pCaller->m_hWnd,GWL_EXSTYLE,dwStyle); if(retVal == 0) { log(LOGGING_LOW, L"WM_W32SETPARENTWINDOWTRANSPERANCY SetWindowLong Failed! It returned 0"); return 0; } // Set the Alpha Channel on the Parent Window if(!SetLayeredWindowAttributes(pCaller->m_hWnd,RGB(0,0,0),(BYTE)lParam,LWA_ALPHA)) { log(LOGGING_LOW, L"WM_W32SETPARENTWINDOWTRANSPERANCY SetLayeredWindowAttributes Failed! It returned false"); return 0; } // Also Set it on the Button (let it become transparent/untransparent with the hooked window) pCaller->SetAlpha((BYTE)lParam); } return 1; case WM_W32SETPARENTWINDOWACTIVE: { HWND hWnd = ::SetActiveWindow(pCaller->m_hWnd); if(hWnd == NULL) return 0; else return 1; } return 0; case WM_W32POSTMESSAGETOPARENTWINDOW: { if((int)lParam != 0) { wchar_t buf[MAX_PATH] = {0}; if(GlobalGetAtomName((ATOM) lParam, buf,MAX_PATH) == 0) { log(LOGGING_DEBUG, L"*Error* - WM_W32SENDMESSAGETOPARENTWINDOW received no String from Atom"); } else { HWND hWndM = NULL; UINT MsgM = 0; WPARAM wParamM = 0; LPARAM lParamM = 0; int IntsM[4] = {0}; bool bParseFailed = false; log(LOGGING_DEBUG, L"WM_W32SENDMESSAGETOPARENTWINDOW received String from Atom %s", buf); wchar_t* pToken = wcstok(buf, L";"); for (int i = 0; (pToken != NULL) && (i < 4); ++i) { log(LOGGING_DEBUG, L"WM_W32SENDMESSAGETOPARENTWINDOW Token %d - %s", (i + 1), pToken); IntsM[i] = _wtoi(pToken); if(((i == 0) || (i == 1))&& (IntsM[i] == 0)) { log(LOGGING_DEBUG, L"*Error* - WM_W32SENDMESSAGETOPARENTWINDOW token %d is invalid", (i + 1)); bParseFailed = true; } pToken = wcstok(NULL, L";"); } if(!bParseFailed) { hWndM = (HWND) IntsM[0]; MsgM = (UINT) IntsM[1]; wParamM = (WPARAM) IntsM[2]; lParamM = (LPARAM) IntsM[3]; if(IsWindow(hWndM)) { log(LOGGING_DEBUG, L"WM_W32SENDMESSAGETOPARENTWINDOW Posting Message to hWnd %d, Msg %d, wParam %d, lParam %i", hWndM, MsgM, wParamM, lParamM); PostMessage(hWndM,MsgM,wParamM,lParamM); return 1; } else log(LOGGING_DEBUG, L"*Error* - WM_W32SENDMESSAGETOPARENTWINDOW window handle %d is invalid", hWndM); } return 0; } } else { log(LOGGING_DEBUG, L"*Error* - WM_W32SENDMESSAGETOPARENTWINDOW received no Atom"); } } return 0; } } catch(...) { log(LOGGING_LOW, L"*Error* - A fatal error occured in W32WndProc for W32Button"); } HANDLE_MESSAGE: LRESULT lr = DefWindowProc(hWnd,Msg,wParam,lParam); return (lr); } ////////////////////////////////////////////////////////////////////// // Func: WPFThreadProc (WPFButtonForm) // Desc: Responsible for handling messages that are supposed to go to the WPFButtonForm ////////////////////////////////////////////////////////////////////// DWORD WINAPI WPFThreadProc(__in LPVOID lpParameter) { CoInitialize(NULL); MSG msg; // Force the system to create a message queue for the thread PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); IButtonFormPtr wpfForm; IClientEventsPtr EventDispatcherForwarder; WPFCaller* pCaller = (WPFCaller*) lpParameter; // handle to parent window (Hooked Window) HWND hWnd = pCaller->m_hWnd; // First thing we want to do is read the icon setting for the win32 button // *we are doing it here because we are reading the settings using COM, // and this thread is COM enabled if(!ReadButtonThemeSetting(pCaller)) return -1; // We need to sit here idly and wait to make sure that we got the Button Window Handle MakeSureW32ButtonWindowGotCreated(pCaller); HWND hWndButton = pCaller->m_hwndW32Window; // handle to the Button Window log(LOGGING_DEBUG, L"WPFThreadProc: Wait over. ButtonW32 Window got created (%u).", hWndButton); // Try to create a WPF Form Objects HRESULT hr = wpfForm.CreateInstance(__uuidof(ButtonWPFormCCW)); if(SUCCEEDED(hr)) { try { // Let's get the IClientEvents Interface to forward Window Client Events log(LOGGING_HIGH, L"Trying to query for IClientEvents Interface"); HRESULT hr = wpfForm.QueryInterface(__uuidof(IClientEvents), &EventDispatcherForwarder); if(SUCCEEDED(hr)) log(LOGGING_HIGH, L"Querying for IClientEvents succeeded"); else log(LOGGING_LOW, L"Querying for IClientEvents failed"); // Let the WPF form know that we activated (and pass out the window handle // that activated it) ~this is importanted for housekeeping. also pass out the // Button Handle so that it can set it's state. ~Imp log(LOGGING_DEBUG, L"Calling WPFThreadProc_ButtonWPForm_Activated Method with Parent (%u) and Button (%u)", hWnd, hWndButton); wpfForm->WPFThreadProc_ButtonWPForm_Activated((long)hWnd, (long) hWndButton); log(LOGGING_HIGH, L"Calling WPFThreadProc_ButtonWPForm_Activated Method Returned - Everything is O.K."); // Also let the Client Event know the same thing log(LOGGING_DEBUG, L"WM_CLIENT_EVENT hWndInitialActivate() forwarding it to WPFButton for PID %u and hWnd %u with CommandLine %s", ::GetCurrentProcessId(), hWnd, ::GetCommandLine()); _bstr_t CommandLinePrms(GetCommandLine()); EventDispatcherForwarder->hWndInitialActivate(GetCurrentProcessId(), (long)hWnd,CommandLinePrms); log(LOGGING_HIGH, L"WM_CLIENT_EVENT hWndInitialActivate() returned - Everything is O.K."); // Let OnDefault know that this Thread is ready to receive messages pCaller->m_bThreadIsReady = true; BOOL bRet = FALSE; while( (bRet = GetMessage( &msg, (HWND)-1, 0, 0 )) != 0) { if (bRet == -1) { // handle the error and possibly exit log(LOGGING_LOW, L"A Fatal Error Occured in ThreadProc. Exiting!", hr); goto STOPPING_THREAD; break; } else { //// // MAIN MESSAGE LOOP //// switch(msg.message) { case WM_KILLTHREAD: log(LOGGING_MEDIUM, L"WM_KILLTHREAD was Posted to Thread. Exiting Window(%u) in pid(%u).", pCaller->m_hWnd, GetCurrentProcessId()); pCaller->m_hwndWPForm = NULL; goto STOPPING_THREAD; break; case WM_GETBUTTONTHEMESETTING: if(ReadButtonThemeSetting(pCaller)) log(LOGGING_HIGH, L"Received GetButtonThemeSetting Message - Read ButtonThemeSettings Succeeded"); else log(LOGGING_LOW, L"Received GetButtonThemeSetting Message - Read ButtonThemeSettings Failed!"); break; case WM_W32BUTTONCLICKED: log(LOGGING_DEBUG, L"WM_W32BUTTONCLICKED passing down to WPFForm->W32BUTTONCLICKED()"); pCaller->m_hwndWPForm = (HWND) wpfForm->W32BUTTONCLICKED(); break; case (WM_USER + WM_ACTIVATE): if((LOWORD(msg.wParam) == WA_ACTIVE) || (LOWORD(msg.wParam) == WA_CLICKACTIVE)) { if(HIWORD(msg.wParam) == 0) wpfForm->WindowEvent_WindowActivated(); } else if(LOWORD(msg.wParam) == WA_INACTIVE) { if(HIWORD(msg.wParam) == 0) wpfForm->WindowEvent_WindowDeactivated(); } break; case (WM_USER + WM_SIZE): if(msg.wParam == SIZE_MAXIMIZED) wpfForm->WindowEvent_MaximizeOccured((long)HIWORD(msg.lParam),(long)LOWORD(msg.lParam)); else if(msg.wParam == SIZE_MINIMIZED) wpfForm->WindowEvent_MinimizeOccured(); break; //// // Client Window Events * WPFThreadProc sends them to a Event Dispatcher // via COM to let C# know about these * //// case (WM_CLIENT_EVENT_MDICHILDWINDOWSWITCHOCCURED): case (WM_CLIENT_EVENT_PARENTGETSETTEXTOCCURED): case (WM_CLIENT_EVENT_PARENTSETFOCUSOCCURED): { log(LOGGING_DEBUG, L"WM_CLIENT_EVENT ReResolve() forwarding it to WPFButton"); EventDispatcherForwarder->ReResolve(GetCurrentProcessId(),(long)hWnd); // Ideally only switches of active document occur this way } break; case (WM_CLIENT_EVENT_PARENTNOTIFYLBUTTONCLICKOCCURED): case (WM_CLIENT_EVENT_PARENTNOTIFYWMCREATEOCCURED): case (WM_CLIENT_EVENT_PARENTCLOSEWITHOUTDESTROYOCCURED): { log(LOGGING_DEBUG, L"WM_CLIENT_EVENT PossArtifactAddedDeleted() forwarding it to WPFButton"); //EventDispatcherForwarder->ReResolve(GetCurrentProcessId(),(long)hWnd); } break; case (WM_CLIENT_EVENT_DRAGNDROPOCCURED): { log(LOGGING_DEBUG, L"WM_CLIENT_EVENT DragNDropOccured() forwarding it to WPFButton"); EventDispatcherForwarder->DragNDropOccured(GetCurrentProcessId(),(long)hWnd,pCaller->m_nDragDroppedFilesCount,pCaller->m_bstrDragDroppedFilesSemiColonSep); } break; case (WM_CLIENT_EVENT_OPENFILEORSAVEASDIALOGOCCURED): { log(LOGGING_DEBUG, L"WM_CLIENT_EVENT OpenOrSaveFileDialogOccured() forwarding it to WPFButton"); _bstr_t possLocAndFileName(pCaller->m_strFileNameLocationBuf); _bstr_t FileTypes(pCaller->m_strFileTypesBuf); EventDispatcherForwarder->OpenOrSaveFileDialogOccured(GetCurrentProcessId(), (long)hWnd, possLocAndFileName, FileTypes); } break; } } } // Let the WPF Form know that this thread is exiting to allow it to do // any cleanup it may want to do STOPPING_THREAD: log(LOGGING_LOW, L"EventDispatcherHandler - hWindow %u Deactivation Occured for PID %u", hWnd,::GetCurrentProcessId()); wpfForm->WPFThreadProc_ButtonWPForm_Deactivated(); // Also let the Client Event know the same thing log(LOGGING_DEBUG, L"WM_CLIENT_EVENT hWndLastDeactivate() forwarding it to WPFButton for PID %u and hWnd %u", ::GetCurrentProcessId(), hWnd); EventDispatcherForwarder->hWndLastDeactivate(GetCurrentProcessId(), (long)hWnd); } catch(...) { log(LOGGING_LOW, L"*Error* - A fatal Error Occured in WPFThreadProc WpfButton. Exiting..."); } } else { log(LOGGING_LOW, L"Error occured creating WPForm Instance COM %i. Exiting...",hr); } pCaller->m_bThreadIsReady = false; CoUninitialize(); return 0; } //******************************************************************** // Class WPFCaller // // Programmer Daniel Romischer // Ooganizer Development Team // // Description ~This class holds the default wndproc to process all the // messages and send them to the WPF COM Object via the COM Thread. // // Note ~ All the host's Windows Messages enter in on OnDefault() // // Created 05-26-2008 //******************************************************************** ////////////////////////////////////////////////////////////////////// //******************************************************************** // Construction / Destruction //******************************************************************** ////////////////////////////////////////////////////////////////////// WPFCaller::WPFCaller(HookWndItem Item, HINSTANCE hInstance): m_hWnd(Item.hWnd), m_hwndWPForm(NULL), m_hwndW32Window(NULL), m_DefWndProc(Item.DefWndProc), m_HookWndProc(Item.HookWndProc), m_thread(NULL), m_threadID(0), m_bThreadIsReady(false), m_hInst(hInstance), m_wndHeight(0), m_wndWidth(0), m_ButtonState(BUTTON_NONE), m_ButtonToggledState(BUTTON_NONE), m_ButtonStateLastSaved(BUTTON_NONE), m_ButtonToggledStateLastSaved(BUTTON_NONE), m_bButtonIsArtificiallyHidden_MOVE(false), m_bButtonIsMinimized(false), m_bButtonIsBeingRestored(false), m_StartCloseButNotDestroyTimerCount(0), m_nDragDroppedFilesCount(0) { // Allow us access to the static DS *only load once* if(s_WPFCallerAccess == NULL) s_WPFCallerAccess = new CWHPaccess(); // just to make sure (to avoid any thread issues) assign out the this instance // BEFORE creating the threads *below*. Item.wpfcaller = this; if(s_WPFCallerAccess->SetHookedWndItem(Item.hWnd, Item) == FALSE) log(LOGGING_LOW, L"WPFCaller SetHookedWndItem FAILED!! (THIS IS NOT GOOD!)"); // Create the COM WPF Object for WPF Power and Flexibility m_thread = CreateThread(NULL,0,&WPFThreadProc,(LPVOID)this,0,&m_threadID); if(m_threadID > 0) log(LOGGING_HIGH, L"WPFCaller Spawned WPFThreadID %i",m_threadID); else log(LOGGING_LOW, L"Error - WPFCaller Failed to Spawn WPFThread",m_threadID); // Clear FileName/Types Buffer memset(m_strFileNameLocationBuf,0,(MAX_PATH + 1)); memset(m_strFileTypesBuf,0,(MAX_PATH + 1)); // We are creating a new Thread to deal with 2 windows (WPForm Window, // Start Window32 Button (for speed) Start(); } WPFCaller::~WPFCaller() { log(LOGGING_DEBUG, L"~WPFCaller() got called"); // Destroy the Button Window if(m_hwndW32Window && ::IsWindow(m_hwndW32Window)) ::DestroyWindow(m_hwndW32Window); // Destroy the COM Thread if(m_bThreadIsReady) { // First let's make sure the thread is even still running and valid, // an error could have closed down the thread. Let's check if the handle // gives us a valid id. DWORD dwThreadId = GetThreadId(m_thread); bool bThreadIsValid = ((dwThreadId != 0) && (dwThreadId == m_threadID)); // if this thread is not valid no need to continue below if(!bThreadIsValid) log(LOGGING_LOW, L"WPFThread ID %i was invalid when compared to Id %i, ~thread must already have been closed. An error must have occured", m_threadID, dwThreadId); if(bThreadIsValid) { // Pass QUIT Message to the COM Thread log(LOGGING_DEBUG, L"Posting WM_KILLTHREAD Message to COM Thread %i", m_threadID); PostThreadMessage(m_threadID, WM_KILLTHREAD, NULL, NULL); // We need to wait to make sure COM deallocated before exiting, // otherwise the WPForm remains active, because deallocation never occurs while(m_bThreadIsReady) Sleep(20); log(LOGGING_DEBUG, L"Closing Thread Handle to Thread %i", m_threadID); CloseHandle(m_thread); // Also Unregister all Window Classes here //UnregisterClass(L"W32ButtonHookWndClass", m_hInst); } else { log(LOGGING_LOW, L"Did NOT close WPFThread m_bThreadIsReady was not true - this should mean WPFThread closed due to an error with COM"); } } else { log(LOGGING_LOW, L"Did NOT close WPFThread m_bThreadIsReady was not true - this should mean WPFThread closed due to an error with COM"); } } ///////////////////////////////////////////////////////////////////// // Desc: Get the Alpha Channel / Opaqueness of the Button ///////////////////////////////////////////////////////////////////// BYTE WPFCaller::GetAlpha() { COLORREF pcrKey; BYTE pbAlpha; DWORD dwFlags; GetLayeredWindowAttributes(m_hwndW32Window, &pcrKey, &pbAlpha, &dwFlags); return pbAlpha; } ///////////////////////////////////////////////////////////////////// // Desc: Set the Alpha Channel / Opaqueness for the Button ///////////////////////////////////////////////////////////////////// void WPFCaller::SetAlpha(BYTE bAlpha) { SetLayeredWindowAttributes(m_hwndW32Window, RGB(255,255,255),bAlpha, LWA_COLORKEY|LWA_ALPHA); } ///////////////////////////////////////////////////////////////////// // Desc: Use this to start the Fade event on the Button ///////////////////////////////////////////////////////////////////// void WPFCaller::StartRestoreEventFadeTimer() { log(LOGGING_DEBUG, L"Launching Restore Event Timer..."); SetAlpha(0); // Hide Button Completly SetTimer(m_hwndW32Window, NID_RESTORE_EVENT_TIMER,20,NULL); } ///////////////////////////////////////////////////////////////////// // Desc: Use this when a WM_CLOSE message is received. Some Apps won't // close when this event occurs (but rather close an artifact), // ~this allows us to detect this occurance ///////////////////////////////////////////////////////////////////// void WPFCaller::StartCloseButNotDestroyTimer() { // quarter of a second is more than enough time to know whether an app closed or not // ~but just in case we'll run the timer multiple times over and over m_StartCloseButNotDestroyTimerCount = 0; log(LOGGING_DEBUG, L"Launching CloseButNotDestroyTimer..."); SetTimer(m_hwndW32Window, NID_CLOSE_BUT_NOT_DESTROY_EVENT_TIMER,250,NULL); } ///////////////////////////////////////////////////////////////////// // Func: EventDispatcherHandler() // Desc: Responsible for sending certain client events to the WPFThreadProc // This allows us to be more interactive. // // Note: Not all Client events are handled here *only the easiest ones* ////////////////////////////////////////////////////////////////////// void WPFCaller::EventDispatcherHandler(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg) { // DragNDropOccured case WM_DROPFILES: { HDROP hdrop = (HDROP) wParam; wchar_t buf[MAX_PATH + 1] = {0}; m_bstrDragDroppedFilesSemiColonSep = L""; m_nDragDroppedFilesCount = DragQueryFile(hdrop,0xFFFFFFFF,NULL,0); //log(LOGGING_DEBUG, L"EventDispatcherHandler - hWindow %u received %d Dropped Files", hWnd, m_nDragDroppedFilesCount); for(int i = 0; i < m_nDragDroppedFilesCount; ++i) { if(i > 0) m_bstrDragDroppedFilesSemiColonSep += L";"; DragQueryFile(hdrop,i,buf,MAX_PATH); //log(LOGGING_DEBUG, L"EventDispatcherHandler - File %u - received dropped file %s", i, (char*) buf); lstrcat((wchar_t *)m_bstrDragDroppedFilesSemiColonSep, buf); } // Let WPFCaller know PostThreadMessage(m_threadID, WM_CLIENT_EVENT_DRAGNDROPOCCURED ,0,0); } break; // MDIChildWindowSwitchOccured case WM_KEYDOWN: { // Did Ctrl + F6 or Ctrl + Tab get pushed, if so a possible MDI // switch occured so relay that information if((wParam == VK_TAB || wParam == VK_F6) && (GetKeyState(VK_CONTROL) < 0)) { // Let WPFCaller know //log(LOGGING_DEBUG, L"EventDispatcherHandler - Ctrl + F6 or Ctrl + Tab got pushed - poss. MDIChildWindowSwitch occured"); PostThreadMessage(m_threadID, WM_CLIENT_EVENT_MDICHILDWINDOWSWITCHOCCURED ,0,0); } } break; // ParentGetSetTextOccured case WM_GETTEXT: case WM_SETTEXT: { // Let WPFCaller know //log(LOGGING_DEBUG, L"EventDispatcherHandler - ParentGetSetText Occured - poss. MDIChildWindowSwitch occured"); PostThreadMessage(m_threadID, WM_CLIENT_EVENT_PARENTGETSETTEXTOCCURED,0,0); } break; // ParentNotifyLButtonClick Occured // ParentNotifyWMCreate Occured case WM_PARENTNOTIFY: { WORD wMsg = LOWORD(wParam); switch(wMsg) { case WM_CREATE: { // Let WPFCaller know //log(LOGGING_DEBUG, L"EventDispatcherHandler - ParentNotify WM_CREATE Occured - poss. MDIChildWindow Creation occured"); PostThreadMessage(m_threadID, WM_CLIENT_EVENT_PARENTNOTIFYWMCREATEOCCURED,0,0); } break; case WM_LBUTTONDOWN: { // Let WPFCaller know //log(LOGGING_DEBUG, L"EventDispatcherHandler - ParentNotify LBUTTONDOWN Occured - poss. MDIChildWindowSwitch occured"); PostThreadMessage(m_threadID, WM_CLIENT_EVENT_PARENTNOTIFYLBUTTONCLICKOCCURED,0,0); } break; } } break; // Parent received a WM_CLOSE but didn't destroy itself case WM_CLOSE: { //log(LOGGING_DEBUG, L"EventDispatcherHandler - Received WM_CLOSE running StartCloseButNotDestroyTimer()"); StartCloseButNotDestroyTimer(); // Win32Button will let the WPFCaller know } break; // Parent received focus message case WM_SETFOCUS: { // Let WPFCaller know //log(LOGGING_DEBUG, L"EventDispatcherHandler - Parent SetFocus Occured - poss. MDIChildWindowSwitch occured"); PostThreadMessage(m_threadID, WM_CLIENT_EVENT_PARENTSETFOCUSOCCURED,0,0); } break; } } ///////////////////////////////////////////////////////////////////// // Func: HandleDDEMessages() // Desc: Because DDE is a message-based protocol, it employs no functions or libraries. // All DDE transactions are conducted by passing certain defined DDE messages between the // client and server windows. // ~We may need to use this for certain applications to listen in // // Note: We call this OnDefault(), because DDE message can get passed into us before WPFThread is up and running ////////////////////////////////////////////////////////////////////// void WPFCaller::HandleDDEMessages(HWND hWnd, UINT Msg,WPARAM wParam,LPARAM lParam) { switch(Msg) { case WM_DDE_ACK: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_ACK"); break; case WM_DDE_ADVISE: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_ADVISE"); break; case WM_DDE_DATA: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_DATA"); break; case WM_DDE_EXECUTE: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_EXECUTE"); break; case WM_DDE_INITIATE: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_INITIATE"); { wchar_t strBuf[MAX_PATH] = {0}; ATOM lowword = LOWORD(lParam); ATOM highword = HIWORD(lParam); if(lowword != NULL) { int nSize = ::GlobalGetAtomName((ATOM)lowword,strBuf,MAX_PATH); strBuf[nSize] = 0; //log(LOGGING_DEBUG, L"Found lowword Atom, %d, with lowword String, %s", lowword, strBuf); } if(highword != NULL) { int nSize = ::GlobalGetAtomName((ATOM)highword,strBuf,MAX_PATH); strBuf[nSize] = 0; //log(LOGGING_DEBUG, L"Found highword Atom, %d, with highword String, %s", highword, strBuf); } } break; case WM_DDE_POKE: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_POKE"); break; case WM_DDE_REQUEST: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_REQUEST"); break; case WM_DDE_TERMINATE: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_TERMINATE"); break; case WM_DDE_UNADVISE: //log(LOGGING_DEBUG, L"Received DDE Message WM_DDE_UNADVISE"); break; } } ///////////////////////////////////////////////////////////////////// // Func: OnDefault() // Desc: Default handler for any WM_message received (ALL except WM_NCDESTROY // and m_msgWMUnHook) the Destroy Messages delete this class and are handled // therefore outside of it ////////////////////////////////////////////////////////////////////// LRESULT WPFCaller::OnDefault(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam) { LRESULT result = 0; try { // Handle DDE Messages HandleDDEMessages(hWnd,Msg,wParam,lParam); // If this is a custom message forward it our Button, // it will be the main handler for our custom messages because it can communicate // to the COMThread as well as manipulate the Button (we can skip and return the value) if((m_hwndW32Window != NULL) && (g_IsMessageACustomMessage(Msg))) { return SendMessage(m_hwndW32Window,Msg,wParam,lParam); } // regular messages that our W32Button Cares about it, send it (no WM_USER offset needed here) if((m_hwndW32Window != NULL) && (g_IsMessageW32WindowCaresAbout(Msg))) { // work-around for Minimize/Restore. Let the parent communicate // to the child that it is being redrawn if(Msg == WM_PAINT && m_bButtonIsMinimized) { wParam = (WPARAM) m_hWnd; lParam = (LPARAM) m_hWnd; PostMessage(m_hwndW32Window, Msg, wParam, lParam); } else { SendMessage(m_hwndW32Window, Msg, wParam, lParam); } } // We pass the Messages we care about forward to our COM/W32/WPF Thread if(m_bThreadIsReady && (m_threadID != 0)) { // IF our COM object cares about this, send it if(g_IsMessageWPFormCaresAbout(Msg)) { // We error if we use PostThreadMessage with message < WM_USER so we offset it PostThreadMessage(m_threadID,(WM_USER + Msg),wParam,lParam); } // our Event Dispatcher maybe interested in this // message let it know about it EventDispatcherHandler(hWnd,Msg,wParam,lParam); } // Call the Default Window Proc - IMPORTANT - Always do this result = CallWindowProc(m_DefWndProc, hWnd, Msg, wParam, lParam); } catch(...) { log(LOGGING_LOW, L"*Error* - A Fatal Error occured in WpfCaller's OnDefault() - (This can't be good)"); } return result; } ////////////////////////////////////////////////////////////////////// // Func: threadproc // Desc: Thread Entry Point responsible for Dispatching the W32Window Messages // // Note: We are on purpose Ignoring Error Msg 1410. If the window class is already registered it // will throw an error, we can safely continuen and instantiate the window class ////////////////////////////////////////////////////////////////////// int WINAPI WPFCaller::threadproc(HINSTANCE hinstance, HINSTANCE hPrevInstance, void* vArg, int nCmdShow) { if (!InitApplication(hinstance)) { // "Class already exists" this happens if the // same window get's called again to be hooked into. // this error can safely be ignored. if(!(GetLastError() == 1410)) { log(LOGGING_LOW, L"initApplication failed error %i",GetLastError()); return FALSE; } } if (!InitInstance(hinstance, nCmdShow)) { log(LOGGING_LOW, L"initInstance failed error %i",GetLastError()); return FALSE; } MSG msg; BOOL bGotMessage; m_bThreadIsReady = true; // Thread is up N' Ready and Receiving Messages while ((bGotMessage = GetMessage(&msg, (HWND) NULL, 0, 0)) != 0 ) { if(bGotMessage == -1) { // handle the error and possible exit log(LOGGING_LOW, L"An Error Occured in ThreadProc Messaging. Exiting!"); ExitThread(0); } else { TranslateMessage(&msg); DispatchMessage(&msg); } } return msg.wParam; } ////////////////////////////////////////////////////////////////////// // Func: InitApplication() // Desc: Responsible for setting up the WNDCLASSEX struct and register // the transparent window with Windows via RegisterClassEx() // // Retr: TRUE, if successful, FALSE otherwise ////////////////////////////////////////////////////////////////////// BOOL WPFCaller::InitApplication(HINSTANCE hInstance) { // Fill in the window class structure with parameters // that describe the main window. WNDCLASSEX wcx; wcx.cbSize = sizeof(wcx); // size of structure wcx.style = CS_HREDRAW | CS_VREDRAW; // redraw if size changes wcx.lpfnWndProc = (WNDPROC) W32WndProc; // points to W32 window procedure wcx.cbClsExtra = 0; // no extra class memory wcx.cbWndExtra = 0; // no extra window memory wcx.hInstance = hInstance; // handle to instance wcx.hIcon = NULL; wcx.hCursor = LoadCursor(NULL, IDC_ARROW); // predefined arrow wcx.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); // white background brush wcx.lpszMenuName = L"MainMenu"; // name of menu resource wcx.lpszClassName = L"W32ButtonHookWndClass"; // name of window class wcx.hIconSm = NULL; // Register the window class. *may throw error 1410* return RegisterClassEx(&wcx); } ////////////////////////////////////////////////////////////////////// // Func: InitInstance() // Desc: Responsible for Creating and Showing a new Window Class Instance // // Retr: TRUE, if successful, FALSE otherwise ////////////////////////////////////////////////////////////////////// BOOL WPFCaller::InitInstance(HINSTANCE hInstance, int nCmdShow) { // Get the Dimensions of the hooked in Window RECT rect; GetWindowRect(m_hWnd,&rect); // set owner/parent window HWND hParent = m_hWnd; DWORD dwStyle; //dwStyle = ( WS_POPUP | WS_BORDER ); // For Debugging dwStyle = ( WS_POPUP ); // If parent Window get's hidden for some reason, should we hide this window? // by default owned windows don't hide, when their parent is hidden, // they do hide when parent is minimized. log(LOGGING_DEBUG, L"Calling CreateWindowEx with Parent %i", hParent); //( WS_POPUP | WS_BORDER ), // top-level window m_hwndW32Window = CreateWindowEx( ( WS_EX_LAYERED ), // A Layered window is by default transparent L"W32ButtonHookWndClass", // name of window class L"W32ButtonHookWndTitle",// title-bar string dwStyle, // top-level window rect.left, // default horizontal position rect.top, // default vertical position (rect.right - rect.left), // default width (rect.bottom - rect.top), // default height (HWND) hParent, // set owner window // ~An owned window is always above its owner in the Z order and // is hidden when its owner is minimized (HMENU) NULL, // use class menu hInstance, // handle to application instance (LPVOID) NULL); // no window-creation data log(LOGGING_DEBUG, L"CreateWindowEx returns (m_hwndW32Window is set to %i) ", m_hwndW32Window); // we can use this to fade the window if we want too... (something to think about...) //if(SetLayeredWindowAttributes(m_hwndW32Window, RGB(255,255,255),255, LWA_COLORKEY|LWA_ALPHA)){} SetLayeredWindowAttributes(m_hwndW32Window, RGB(255,255,255),255, LWA_COLORKEY|LWA_ALPHA); // Show the window and send a WM_PAINT message to the window procedure. ShowWindow(m_hwndW32Window, nCmdShow); UpdateWindow(m_hwndW32Window); return (m_hwndW32Window != NULL); }