1417 lines
51 KiB
C++
1417 lines
51 KiB
C++
#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);
|
|
} |