/* Miranda IM Help Plugin Copyright (C) 2002 Richard Hughes, 2005-2007 H. Herkenrath This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program (Help-License.txt); if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "stdafx.h" #define SC_CONTEXTHELP_SEPARATOR SC_SEPARATOR+1 #define SC_CONTEXTHELP_DIALOG SC_CONTEXTHELP+1 #define PROP_CONTEXTSTATE L"HelpPlugin_ContextState" #define PROPF_MENUFORCED 0x01 // always show help context menu for ctl (override default) #define PROPF_MENUDISABLED 0x02 // never show help context menu for ctl #define PROPF_AUTOTIPFORCED 0x04 // always show autotip for ctl (override default) #define PROPF_AUTOTIPDISABLED 0x08 // never show autotip for ctl extern HINSTANCE hInst; extern HWND hwndHelpDlg; extern WORD settingAutoTipDelay; static HHOOK hMessageHook, hKeyboardHook, hEatNextMouseHook = NULL; static HANDLE hServiceShowHelp, hServiceSetContext; struct DlgBoxSubclassData { HWND hwndDlg; WNDPROC pfnOldWndProc; DWORD flags; } static *dlgBoxSubclass = NULL; static int dlgBoxSubclassCount = 0; static CRITICAL_SECTION csDlgBoxSubclass; #define DBSDF_MINIMIZABLE 0x01 // WS_MINIMIZEBOX style was set on hooked window #define DBSDF_MAXIMIZABLE 0x02 // WS_MAXIMIZEBOX style was set on hooked window struct FindChildAtPointData { HWND hwnd; POINT pt; int bestArea; }; // ChildWindowFromPoint() messes up with group boxes static BOOL CALLBACK FindChildAtPointEnumProc(HWND hwnd, LPARAM lParam) { if (IsWindowVisible(hwnd)) { struct FindChildAtPointData *fcap = (struct FindChildAtPointData*)lParam; RECT rcVisible, rc, rcParent; GetWindowRect(hwnd, &rc); GetWindowRect(GetParent(hwnd), &rcParent); IntersectRect(&rcVisible, &rcParent, &rc); if (PtInRect(&rcVisible, fcap->pt)) { int thisArea = (rc.bottom - rc.top)*(rc.right - rc.left); if (thisArea && (thisAreabestArea || fcap->bestArea == 0)) { fcap->bestArea = thisArea; fcap->hwnd = hwnd; } } } return TRUE; } // IsChild() messes up with owned windows int IsRealChild(HWND hwndParent, HWND hwnd) { while (hwnd != NULL) { if (hwnd == hwndParent) return 1; if (hwndParent == GetWindow(hwnd, GW_OWNER)) return 0; hwnd = GetParent(hwnd); } return 0; } static BOOL CALLBACK RemovePropForAllChildsEnumProc(HWND hwnd, LPARAM lParam) { RemoveProp(hwnd, (TCHAR*)lParam); return TRUE; } static HWND hwndMouseMoveDlg = NULL; static UINT idMouseMoveTimer = 0; static LONG cursorPos = MAKELONG(-1, -1); static int openedAutoTip = 0; static void CALLBACK NoMouseMoveForDelayTimerProc(HWND hwnd, UINT, UINT_PTR idTimer, DWORD) { POINT pt; HWND hwndCtl; struct FindChildAtPointData fcap; KillTimer(hwnd, idTimer); if (idMouseMoveTimer != idTimer) return; idMouseMoveTimer = 0; if (!settingAutoTipDelay || !IsWindow(hwndMouseMoveDlg)) return; ZeroMemory(&fcap, sizeof(fcap)); if (!GetCursorPos(&pt)) return; // ChildWindowFromPoint() messes up with group boxes fcap.hwnd = NULL; fcap.pt = pt; EnumChildWindows(hwndMouseMoveDlg, FindChildAtPointEnumProc, (LPARAM)&fcap); hwndCtl = fcap.hwnd; if (hwndCtl == NULL) { ScreenToClient(hwndMouseMoveDlg, &pt); hwndCtl = ChildWindowFromPointEx(hwndMouseMoveDlg, pt, CWP_SKIPINVISIBLE); if (hwndCtl == NULL) return; } LONG_PTR flags = (LONG_PTR)GetProp(hwndCtl, PROP_CONTEXTSTATE); if (flags&PROPF_AUTOTIPDISABLED) return; flags = SendMessage(hwndCtl, WM_GETDLGCODE, (WPARAM)VK_RETURN, (LPARAM)NULL); if (flags&DLGC_HASSETSEL || flags&DLGC_WANTALLKEYS) return; // autotips on edits are annoying CURSORINFO ci; BOOL(WINAPI *pfnGetCursorInfo)(CURSORINFO*); ci.cbSize = sizeof(ci); *(FARPROC*)&pfnGetCursorInfo = GetProcAddress(GetModuleHandleA("USER32"), "GetCursorInfo"); // if(pfnGetCursorInfo && IsWinVer2000Plus()) // call not safe for WinNT4 if (pfnGetCursorInfo(&ci) && !(ci.flags&CURSOR_SHOWING)) return; if (IsRealChild(hwndMouseMoveDlg, hwndCtl) && hwndHelpDlg == NULL) { hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); if (hwndHelpDlg == NULL) return; openedAutoTip = 1; PostMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)hwndCtl); } } static LRESULT CALLBACK KeyboardInputHookProc(int code, WPARAM wParam, LPARAM lParam) { if (code == HC_ACTION && idMouseMoveTimer != 0) { KillTimer(NULL, idMouseMoveTimer); idMouseMoveTimer = 0; } return CallNextHookEx(hKeyboardHook, code, wParam, lParam); } // workaround for WM_HELP (SC_CONTEXTHELP causes an additional WM_LBUTTONUP when selecting) static LRESULT CALLBACK EatNextMouseButtonUpHookProc(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0 && wParam == WM_LBUTTONUP) { UnhookWindowsHookEx(hEatNextMouseHook); // unhook ourselves hEatNextMouseHook = NULL; return -1; } return CallNextHookEx(hEatNextMouseHook, code, wParam, lParam); } static LRESULT CALLBACK DialogBoxSubclassProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { int i; EnterCriticalSection(&csDlgBoxSubclass); for (i = 0; i10) return 0; if (idMouseMoveTimer) KillTimer(NULL, idMouseMoveTimer); idMouseMoveTimer = 0; ZeroMemory(&fcap, sizeof(fcap)); POINTSTOPOINT(pt, MAKEPOINTS(lParam)); // ChildWindowFromPoint() messes up with group boxes fcap.hwnd = NULL; fcap.pt = pt; EnumChildWindows(hwndDlg, FindChildAtPointEnumProc, (LPARAM)&fcap); HWND hwndCtl = fcap.hwnd; if (hwndCtl == NULL) { ScreenToClient(hwndDlg, &pt); hwndCtl = ChildWindowFromPointEx(hwndDlg, pt, CWP_SKIPINVISIBLE); if (hwndCtl == NULL) break; POINTSTOPOINT(pt, MAKEPOINTS(lParam)); } { LONG_PTR flags = (LONG_PTR)GetProp(hwndCtl, PROP_CONTEXTSTATE); if (flags&PROPF_MENUDISABLED) break; else if (!(flags&PROPF_MENUFORCED)) { int type = GetControlType(hwndCtl); // showing a context menu on these looks silly (multi components) if (type == CTLTYPE_TOOLBAR || type == CTLTYPE_LISTVIEW || type == CTLTYPE_TREEVIEW || type == CTLTYPE_STATUSBAR || type == CTLTYPE_CLC) break; } } if (IsRealChild(hwndDlg, hwndCtl)) { HMENU hMenu = CreatePopupMenu(); AppendMenu(hMenu, MF_STRING, SC_CONTEXTHELP, (hwndCtl == hwndDlg) ? TranslateT("&What's this dialog?") : TranslateT("&What's this?")); if (TrackPopupMenuEx(hMenu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_HORPOSANIMATION | TPM_VERPOSANIMATION | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, hwndDlg, NULL)) { if (hwndHelpDlg == NULL) { hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); if (hwndHelpDlg == NULL) { DestroyMenu(hMenu); break; } } SendMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)hwndCtl); } DestroyMenu(hMenu); } return 0; } case WM_HELP: { HELPINFO *hi = (HELPINFO*)lParam; if (hi->iContextType != HELPINFO_WINDOW) break; // fix for SHBrowseForFolder() dialog, which sends unhandled help to parent if (!IsRealChild(hwndDlg, (HWND)hi->hItemHandle)) break; if (idMouseMoveTimer) KillTimer(NULL, idMouseMoveTimer); idMouseMoveTimer = 0; if (!IsWindow(hwndHelpDlg)) { hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); if (hwndHelpDlg == NULL) break; } SendMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)hi->hItemHandle); // we need to eat the next WM_LBUTTONDOWN (if invoked by mouse) if (GetKeyState(GetSystemMetrics(SM_SWAPBUTTON) ? VK_RBUTTON : VK_LBUTTON) & 0x8000 && hEatNextMouseHook == NULL) hEatNextMouseHook = SetWindowsHookEx(WH_MOUSE, EatNextMouseButtonUpHookProc, NULL, GetCurrentThreadId()); return TRUE; } case WM_NCDESTROY: if (idMouseMoveTimer) KillTimer(NULL, idMouseMoveTimer); idMouseMoveTimer = 0; EnumChildWindows(hwndDlg, RemovePropForAllChildsEnumProc, (LPARAM)PROP_CONTEXTSTATE); { TCHAR text[64]; mir_sntprintf(text, _countof(text), L"unhooked window 0x%X for context help\n", hwndDlg); OutputDebugString(text); } SetWindowLongPtr(hwndDlg, GWLP_WNDPROC, (LONG_PTR)pfnWndProc); break; } return CallWindowProc(pfnWndProc, hwndDlg, msg, wParam, lParam); } static LRESULT CALLBACK HelpSendMessageHookProc(int code, WPARAM wParam, LPARAM lParam) { if (code >= 0) { CWPSTRUCT *msg = (CWPSTRUCT*)lParam; switch (msg->message) { case WM_INITDIALOG: // dialogs and message boxes if (GetClassLong(msg->hwnd, GCW_ATOM) != 32770) // class="#32770" break; if (msg->hwnd == hwndHelpDlg || (DLGPROC)GetWindowLongPtr(msg->hwnd, DWLP_DLGPROC) == HelpDlgProc) break; #ifndef EDITOR if ((DLGPROC)GetWindowLongPtr(msg->hwnd, DWLP_DLGPROC) == ShadowDlgProc) break; #endif { LONG_PTR style, exStyle; struct DlgBoxSubclassData *buf; exStyle = GetWindowLongPtr(msg->hwnd, GWL_EXSTYLE); if (exStyle&WS_EX_CONTEXTHELP) break; style = GetWindowLongPtr(msg->hwnd, GWL_STYLE); EnterCriticalSection(&csDlgBoxSubclass); buf = (struct DlgBoxSubclassData*)mir_realloc(dlgBoxSubclass, sizeof(struct DlgBoxSubclassData)*(dlgBoxSubclassCount + 1)); if (buf == NULL) { LeaveCriticalSection(&csDlgBoxSubclass); break; } dlgBoxSubclass = buf; dlgBoxSubclass[dlgBoxSubclassCount].hwndDlg = msg->hwnd; dlgBoxSubclass[dlgBoxSubclassCount].pfnOldWndProc = (WNDPROC)SetWindowLongPtr(msg->hwnd, GWLP_WNDPROC, (LONG_PTR)DialogBoxSubclassProc); dlgBoxSubclass[dlgBoxSubclassCount].flags = 0; // WS_EX_CONTEXTHELP cannot be used in conjunction WS_MINIMIZEBOX or WS_MAXIMIZEBOX // solution: switch off WS_MINIMIZEBOX or WS_MAXIMIZEBOX when only one of them is present if (!(style & WS_MINIMIZEBOX) || !(style & WS_MAXIMIZEBOX)) { if (style & WS_MINIMIZEBOX) dlgBoxSubclass[dlgBoxSubclassCount].flags |= DBSDF_MINIMIZABLE; if (style & WS_MAXIMIZEBOX) dlgBoxSubclass[dlgBoxSubclassCount].flags |= DBSDF_MAXIMIZABLE; SetWindowLongPtr(msg->hwnd, GWL_STYLE, style&(~(WS_MINIMIZEBOX | WS_MAXIMIZEBOX))); SetWindowLongPtr(msg->hwnd, GWL_EXSTYLE, (LONG_PTR)exStyle | WS_EX_CONTEXTHELP); } dlgBoxSubclassCount++; LeaveCriticalSection(&csDlgBoxSubclass); } { HMENU hMenu = GetSystemMenu(msg->hwnd, FALSE); if (hMenu != NULL && AppendMenu(hMenu, MF_SEPARATOR, SC_CONTEXTHELP_SEPARATOR, NULL)) { AppendMenu(hMenu, MF_STRING, SC_CONTEXTHELP, TranslateT("&What's this?")); AppendMenu(hMenu, MF_STRING, SC_CONTEXTHELP_DIALOG, TranslateT("&What's this dialog?")); } } { TCHAR text[64]; mir_sntprintf(text, _countof(text), L"hooked window 0x%X for context help\n", msg->hwnd); OutputDebugString(text); } break; } } return CallNextHookEx(hMessageHook, code, wParam, lParam); } static INT_PTR ServiceShowHelp(WPARAM wParam, LPARAM) { if (!IsWindow((HWND)wParam)) return 1; if (idMouseMoveTimer) KillTimer(NULL, idMouseMoveTimer); idMouseMoveTimer = 0; if (hwndHelpDlg == NULL) { hwndHelpDlg = CreateDialog(hInst, MAKEINTRESOURCE(IDD_HELP), NULL, HelpDlgProc); if (hwndHelpDlg == NULL) return 2; } PostMessage(hwndHelpDlg, M_CHANGEHELPCONTROL, 0, (LPARAM)wParam); return 0; } static INT_PTR ServiceSetContextState(WPARAM wParam, LPARAM lParam) { int i; LONG flags; EnterCriticalSection(&csDlgBoxSubclass); for (i = 0; i < dlgBoxSubclassCount; i++) if (IsRealChild(dlgBoxSubclass[i].hwndDlg, (HWND)wParam)) break; if (i == dlgBoxSubclassCount) { LeaveCriticalSection(&csDlgBoxSubclass); return 2; } LeaveCriticalSection(&csDlgBoxSubclass); flags = (lParam&HCSF_CONTEXTMENU) ? PROPF_MENUFORCED : PROPF_MENUDISABLED; flags |= (lParam&HCSF_AUTOTIP) ? PROPF_AUTOTIPFORCED : PROPF_AUTOTIPDISABLED; return !SetProp((HWND)wParam, PROP_CONTEXTSTATE, (HANDLE)flags); } int InstallDialogBoxHook(void) { InitializeCriticalSection(&csDlgBoxSubclass); hServiceShowHelp = CreateServiceFunction(MS_HELP_SHOWHELP, ServiceShowHelp); hServiceSetContext = CreateServiceFunction(MS_HELP_SETCONTEXTSTATE, ServiceSetContextState); hMessageHook = SetWindowsHookEx(WH_CALLWNDPROC, HelpSendMessageHookProc, NULL, GetCurrentThreadId()); // main thread hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardInputHookProc, NULL, GetCurrentThreadId()); // main thread return hMessageHook == NULL; } int RemoveDialogBoxHook(void) { DestroyServiceFunction(hServiceShowHelp); // does NULL check DestroyServiceFunction(hServiceSetContext); // does NULL check UnhookWindowsHookEx(hMessageHook); if (hKeyboardHook) UnhookWindowsHookEx(hKeyboardHook); if (hEatNextMouseHook) UnhookWindowsHookEx(hEatNextMouseHook); DeleteCriticalSection(&csDlgBoxSubclass); for (int i = 0; i