1143 lines
34 KiB
C
1143 lines
34 KiB
C
/*
|
|
* Windows hook functions
|
|
*
|
|
* Copyright 1994, 1995 Alexandre Julliard
|
|
* 1996 Andrew Lewycky
|
|
*
|
|
* Based on investigations by Alex Korobka
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/*
|
|
* Warning!
|
|
* A HHOOK is a 32-bit handle for compatibility with Windows 3.0 where it was
|
|
* a pointer to the next function. Now it is in fact composed of a USER heap
|
|
* handle in the low 16 bits and of a HOOK_MAGIC value in the high 16 bits.
|
|
*/
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "wingdi.h"
|
|
#include "winuser.h"
|
|
#include "wine/winuser16.h"
|
|
#include "wine/winbase16.h"
|
|
#include "hook.h"
|
|
#include "win.h"
|
|
#include "queue.h"
|
|
#include "user.h"
|
|
#include "heap.h"
|
|
#include "struct32.h"
|
|
#include "winproc.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(hook);
|
|
|
|
#include "pshpack1.h"
|
|
|
|
/* Hook data (pointed to by a HHOOK) */
|
|
typedef struct
|
|
{
|
|
HANDLE16 next; /* 00 Next hook in chain */
|
|
HOOKPROC proc; /* 02 Hook procedure (original) */
|
|
INT16 id; /* 06 Hook id (WH_xxx) */
|
|
HQUEUE16 ownerQueue; /* 08 Owner queue (0 for system hook) */
|
|
HMODULE16 ownerModule; /* 0a Owner module */
|
|
WORD flags; /* 0c flags */
|
|
} HOOKDATA;
|
|
|
|
#include "poppack.h"
|
|
|
|
#define HOOK_MAGIC ((int)'H' | (int)'K' << 8) /* 'HK' */
|
|
|
|
/* This should probably reside in USER heap */
|
|
static HANDLE16 HOOK_systemHooks[WH_NB_HOOKS] = { 0, };
|
|
|
|
/* ### start build ### */
|
|
extern LONG CALLBACK HOOK_CallTo16_long_wwl(HOOKPROC16,WORD,WORD,LONG);
|
|
/* ### stop build ### */
|
|
|
|
|
|
/***********************************************************************
|
|
* call_hook_16
|
|
*/
|
|
inline static LRESULT call_hook_16( HOOKPROC16 proc, INT id, INT code, WPARAM wparam, LPARAM lparam )
|
|
{
|
|
LRESULT ret = HOOK_CallTo16_long_wwl( proc, code, wparam, lparam );
|
|
/* Grrr. While the hook procedure is supposed to have an LRESULT return
|
|
value even in Win16, it seems that for those hook types where the
|
|
return value is interpreted as BOOL, Windows doesn't actually check
|
|
the HIWORD ... Some buggy Win16 programs, notably WINFILE, rely on
|
|
that, because they neglect to clear DX ... */
|
|
if (id != WH_JOURNALPLAYBACK) ret = LOWORD( ret );
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* call_hook_16_to_32
|
|
*
|
|
* Convert hook params to 32-bit and call 32-bit hook procedure
|
|
*/
|
|
static LRESULT call_hook_16_to_32( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam,
|
|
BOOL unicode )
|
|
{
|
|
LRESULT ret = 0;
|
|
|
|
switch( id )
|
|
{
|
|
case WH_MSGFILTER:
|
|
case WH_SYSMSGFILTER:
|
|
case WH_JOURNALRECORD:
|
|
{
|
|
MSG16 *msg16 = MapSL(lparam);
|
|
MSG msg32;
|
|
|
|
STRUCT32_MSG16to32( msg16, &msg32 );
|
|
ret = proc( code, wparam, (LPARAM)&msg32 );
|
|
break;
|
|
}
|
|
|
|
case WH_GETMESSAGE:
|
|
{
|
|
MSG16 *msg16 = MapSL(lparam);
|
|
MSG msg32;
|
|
|
|
STRUCT32_MSG16to32( msg16, &msg32 );
|
|
ret = proc( code, wparam, (LPARAM)&msg32 );
|
|
STRUCT32_MSG32to16( &msg32, msg16 );
|
|
break;
|
|
}
|
|
|
|
case WH_JOURNALPLAYBACK:
|
|
{
|
|
EVENTMSG16 *em16 = MapSL(lparam);
|
|
EVENTMSG em32;
|
|
|
|
em32.message = em16->message;
|
|
em32.paramL = em16->paramL;
|
|
em32.paramH = em16->paramH;
|
|
em32.time = em16->time;
|
|
em32.hwnd = 0; /* FIXME */
|
|
ret = proc( code, wparam, (LPARAM)&em32 );
|
|
break;
|
|
}
|
|
|
|
case WH_CALLWNDPROC:
|
|
{
|
|
CWPSTRUCT16 *cwp16 = MapSL(lparam);
|
|
CWPSTRUCT cwp32;
|
|
|
|
cwp32.hwnd = WIN_Handle32(cwp16->hwnd);
|
|
cwp32.lParam = cwp16->lParam;
|
|
|
|
if (unicode)
|
|
WINPROC_MapMsg16To32W( cwp32.hwnd, cwp16->message, cwp16->wParam,
|
|
&cwp32.message, &cwp32.wParam, &cwp32.lParam );
|
|
else
|
|
WINPROC_MapMsg16To32A( cwp32.hwnd, cwp16->message, cwp16->wParam,
|
|
&cwp32.message, &cwp32.wParam, &cwp32.lParam );
|
|
|
|
ret = proc( code, wparam, (LPARAM)&cwp32 );
|
|
|
|
if (unicode)
|
|
WINPROC_UnmapMsg16To32W( cwp32.hwnd, cwp32.message, cwp32.wParam, cwp32.lParam, 0 );
|
|
else
|
|
WINPROC_UnmapMsg16To32A( cwp32.hwnd, cwp32.message, cwp32.wParam, cwp32.lParam, 0 );
|
|
break;
|
|
}
|
|
|
|
case WH_CBT:
|
|
switch (code)
|
|
{
|
|
case HCBT_CREATEWND:
|
|
{
|
|
CBT_CREATEWNDA cbtcw32;
|
|
CREATESTRUCTA cs32;
|
|
CBT_CREATEWND16 *cbtcw16 = MapSL(lparam);
|
|
CREATESTRUCT16 *cs16 = MapSL( (SEGPTR)cbtcw16->lpcs );
|
|
|
|
cbtcw32.lpcs = &cs32;
|
|
cbtcw32.hwndInsertAfter = WIN_Handle32( cbtcw16->hwndInsertAfter );
|
|
STRUCT32_CREATESTRUCT16to32A( cs16, &cs32 );
|
|
|
|
if (unicode)
|
|
{
|
|
cs32.lpszName = (LPSTR)map_str_16_to_32W( cs16->lpszName );
|
|
cs32.lpszClass = (LPSTR)map_str_16_to_32W( cs16->lpszClass );
|
|
ret = proc( code, wparam, (LPARAM)&cbtcw32 );
|
|
unmap_str_16_to_32W( (LPWSTR)cs32.lpszName );
|
|
unmap_str_16_to_32W( (LPWSTR)cs32.lpszClass );
|
|
}
|
|
else
|
|
{
|
|
cs32.lpszName = MapSL( cs16->lpszName );
|
|
cs32.lpszClass = MapSL( cs16->lpszClass );
|
|
ret = proc( code, wparam, (LPARAM)&cbtcw32 );
|
|
}
|
|
cbtcw16->hwndInsertAfter = WIN_Handle16( cbtcw32.hwndInsertAfter );
|
|
break;
|
|
}
|
|
case HCBT_ACTIVATE:
|
|
{
|
|
CBTACTIVATESTRUCT16 *cas16 = MapSL(lparam);
|
|
CBTACTIVATESTRUCT cas32;
|
|
cas32.fMouse = cas16->fMouse;
|
|
cas32.hWndActive = WIN_Handle32(cas16->hWndActive);
|
|
ret = proc( code, wparam, (LPARAM)&cas32 );
|
|
break;
|
|
}
|
|
case HCBT_CLICKSKIPPED:
|
|
{
|
|
MOUSEHOOKSTRUCT16 *ms16 = MapSL(lparam);
|
|
MOUSEHOOKSTRUCT ms32;
|
|
|
|
ms32.pt.x = ms16->pt.x;
|
|
ms32.pt.y = ms16->pt.y;
|
|
/* wHitTestCode may be negative, so convince compiler to do
|
|
correct sign extension. Yay. :| */
|
|
ms32.wHitTestCode = (INT)(INT16)ms16->wHitTestCode;
|
|
ms32.dwExtraInfo = ms16->dwExtraInfo;
|
|
ms32.hwnd = WIN_Handle32( ms16->hwnd );
|
|
ret = proc( code, wparam, (LPARAM)&ms32 );
|
|
break;
|
|
}
|
|
case HCBT_MOVESIZE:
|
|
{
|
|
RECT16 *rect16 = MapSL(lparam);
|
|
RECT rect32;
|
|
|
|
CONV_RECT16TO32( rect16, &rect32 );
|
|
ret = proc( code, wparam, (LPARAM)&rect32 );
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WH_MOUSE:
|
|
{
|
|
MOUSEHOOKSTRUCT16 *ms16 = MapSL(lparam);
|
|
MOUSEHOOKSTRUCT ms32;
|
|
|
|
ms32.pt.x = ms16->pt.x;
|
|
ms32.pt.y = ms16->pt.y;
|
|
/* wHitTestCode may be negative, so convince compiler to do
|
|
correct sign extension. Yay. :| */
|
|
ms32.wHitTestCode = (INT)((INT16)ms16->wHitTestCode);
|
|
ms32.dwExtraInfo = ms16->dwExtraInfo;
|
|
ms32.hwnd = WIN_Handle32(ms16->hwnd);
|
|
ret = proc( code, wparam, (LPARAM)&ms32 );
|
|
break;
|
|
}
|
|
|
|
case WH_DEBUG:
|
|
{
|
|
DEBUGHOOKINFO16 *dh16 = MapSL(lparam);
|
|
DEBUGHOOKINFO dh32;
|
|
|
|
dh32.idThread = 0; /* FIXME */
|
|
dh32.idThreadInstaller = 0; /* FIXME */
|
|
dh32.lParam = dh16->lParam; /* FIXME Check for sign ext */
|
|
dh32.wParam = dh16->wParam;
|
|
dh32.code = dh16->code;
|
|
|
|
/* do sign extension if it was WH_MSGFILTER */
|
|
if (wparam == 0xffff) wparam = WH_MSGFILTER;
|
|
ret = proc( code, wparam, (LPARAM)&dh32 );
|
|
break;
|
|
}
|
|
|
|
case WH_SHELL:
|
|
case WH_KEYBOARD:
|
|
ret = proc( code, wparam, lparam );
|
|
break;
|
|
|
|
case WH_HARDWARE:
|
|
case WH_FOREGROUNDIDLE:
|
|
case WH_CALLWNDPROCRET:
|
|
default:
|
|
FIXME("\t[%i] 16to32 translation unimplemented\n", id);
|
|
ret = proc( code, wparam, lparam );
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* call_hook_32_to_16
|
|
*
|
|
* Convert hook params to 16-bit and call 16-bit hook procedure
|
|
*/
|
|
static LRESULT call_hook_32_to_16( HOOKPROC16 proc, INT id, INT code, WPARAM wparam, LPARAM lparam,
|
|
BOOL unicode )
|
|
{
|
|
LRESULT ret = 0;
|
|
|
|
switch (id)
|
|
{
|
|
case WH_MSGFILTER:
|
|
case WH_SYSMSGFILTER:
|
|
case WH_JOURNALRECORD:
|
|
{
|
|
MSG *msg32 = (MSG *)lparam;
|
|
MSG16 msg16;
|
|
|
|
STRUCT32_MSG32to16( msg32, &msg16 );
|
|
lparam = MapLS( &msg16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
|
|
case WH_GETMESSAGE:
|
|
{
|
|
MSG *msg32 = (MSG *)lparam;
|
|
MSG16 msg16;
|
|
|
|
STRUCT32_MSG32to16( msg32, &msg16 );
|
|
lparam = MapLS( &msg16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
STRUCT32_MSG16to32( &msg16, msg32 );
|
|
break;
|
|
}
|
|
|
|
case WH_JOURNALPLAYBACK:
|
|
{
|
|
EVENTMSG *em32 = (EVENTMSG *)lparam;
|
|
EVENTMSG16 em16;
|
|
|
|
em16.message = em32->message;
|
|
em16.paramL = em32->paramL;
|
|
em16.paramH = em32->paramH;
|
|
em16.time = em32->time;
|
|
lparam = MapLS( &em16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
|
|
case WH_CALLWNDPROC:
|
|
{
|
|
CWPSTRUCT *cwp32 = (CWPSTRUCT *)lparam;
|
|
CWPSTRUCT16 cwp16;
|
|
MSGPARAM16 mp16;
|
|
|
|
cwp16.hwnd = WIN_Handle16(cwp32->hwnd);
|
|
cwp16.lParam = cwp32->lParam;
|
|
|
|
if (unicode)
|
|
WINPROC_MapMsg32WTo16( cwp32->hwnd, cwp32->message, cwp32->wParam,
|
|
&cwp16.message, &cwp16.wParam, &cwp16.lParam );
|
|
else
|
|
WINPROC_MapMsg32ATo16( cwp32->hwnd, cwp32->message, cwp32->wParam,
|
|
&cwp16.message, &cwp16.wParam, &cwp16.lParam );
|
|
|
|
lparam = MapLS( &cwp16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
|
|
mp16.wParam = cwp16.wParam;
|
|
mp16.lParam = cwp16.lParam;
|
|
mp16.lResult = 0;
|
|
if (unicode)
|
|
WINPROC_UnmapMsg32WTo16( cwp32->hwnd, cwp32->message, cwp32->wParam,
|
|
cwp32->lParam, &mp16 );
|
|
else
|
|
WINPROC_UnmapMsg32ATo16( cwp32->hwnd, cwp32->message, cwp32->wParam,
|
|
cwp32->lParam, &mp16 );
|
|
break;
|
|
}
|
|
|
|
case WH_CBT:
|
|
switch (code)
|
|
{
|
|
case HCBT_CREATEWND:
|
|
{
|
|
CBT_CREATEWNDA *cbtcw32 = (CBT_CREATEWNDA *)lparam;
|
|
CBT_CREATEWND16 cbtcw16;
|
|
CREATESTRUCT16 cs16;
|
|
|
|
STRUCT32_CREATESTRUCT32Ato16( cbtcw32->lpcs, &cs16 );
|
|
cbtcw16.lpcs = (CREATESTRUCT16 *)MapLS( &cs16 );
|
|
cbtcw16.hwndInsertAfter = WIN_Handle16( cbtcw32->hwndInsertAfter );
|
|
lparam = MapLS( &cbtcw16 );
|
|
|
|
if (unicode)
|
|
{
|
|
cs16.lpszName = map_str_32W_to_16( (LPWSTR)cbtcw32->lpcs->lpszName );
|
|
cs16.lpszClass = map_str_32W_to_16( (LPWSTR)cbtcw32->lpcs->lpszClass );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
unmap_str_32W_to_16( cs16.lpszName );
|
|
unmap_str_32W_to_16( cs16.lpszClass );
|
|
}
|
|
else
|
|
{
|
|
cs16.lpszName = MapLS( cbtcw32->lpcs->lpszName );
|
|
cs16.lpszClass = MapLS( cbtcw32->lpcs->lpszClass );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( cs16.lpszName );
|
|
UnMapLS( cs16.lpszClass );
|
|
}
|
|
cbtcw32->hwndInsertAfter = WIN_Handle32( cbtcw16.hwndInsertAfter );
|
|
UnMapLS( (SEGPTR)cbtcw16.lpcs );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
|
|
case HCBT_ACTIVATE:
|
|
{
|
|
CBTACTIVATESTRUCT *cas32 = (CBTACTIVATESTRUCT *)lparam;
|
|
CBTACTIVATESTRUCT16 cas16;
|
|
|
|
cas16.fMouse = cas32->fMouse;
|
|
cas16.hWndActive = WIN_Handle16( cas32->hWndActive );
|
|
|
|
lparam = MapLS( &cas16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
case HCBT_CLICKSKIPPED:
|
|
{
|
|
MOUSEHOOKSTRUCT *ms32 = (MOUSEHOOKSTRUCT *)lparam;
|
|
MOUSEHOOKSTRUCT16 ms16;
|
|
|
|
ms16.pt.x = ms32->pt.x;
|
|
ms16.pt.y = ms32->pt.y;
|
|
ms16.hwnd = WIN_Handle16( ms32->hwnd );
|
|
ms16.wHitTestCode = ms32->wHitTestCode;
|
|
ms16.dwExtraInfo = ms32->dwExtraInfo;
|
|
|
|
lparam = MapLS( &ms16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
case HCBT_MOVESIZE:
|
|
{
|
|
RECT *rect32 = (RECT *)lparam;
|
|
RECT16 rect16;
|
|
|
|
CONV_RECT32TO16( rect32, &rect16 );
|
|
lparam = MapLS( &rect16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WH_MOUSE:
|
|
{
|
|
MOUSEHOOKSTRUCT *ms32 = (MOUSEHOOKSTRUCT *)lparam;
|
|
MOUSEHOOKSTRUCT16 ms16;
|
|
|
|
ms16.pt.x = ms32->pt.x;
|
|
ms16.pt.y = ms32->pt.y;
|
|
ms16.hwnd = WIN_Handle16( ms32->hwnd );
|
|
ms16.wHitTestCode = ms32->wHitTestCode;
|
|
ms16.dwExtraInfo = ms32->dwExtraInfo;
|
|
|
|
lparam = MapLS( &ms16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
|
|
case WH_DEBUG:
|
|
{
|
|
DEBUGHOOKINFO *dh32 = (DEBUGHOOKINFO *)lparam;
|
|
DEBUGHOOKINFO16 dh16;
|
|
|
|
dh16.hModuleHook = 0; /* FIXME */
|
|
dh16.reserved = 0;
|
|
dh16.lParam = dh32->lParam;
|
|
dh16.wParam = dh32->wParam;
|
|
dh16.code = dh32->code;
|
|
|
|
lparam = MapLS( &dh16 );
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
UnMapLS( lparam );
|
|
break;
|
|
}
|
|
|
|
case WH_SHELL:
|
|
case WH_KEYBOARD:
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
break;
|
|
|
|
case WH_HARDWARE:
|
|
case WH_FOREGROUNDIDLE:
|
|
case WH_CALLWNDPROCRET:
|
|
default:
|
|
FIXME("\t[%i] 32to16 translation unimplemented\n", id);
|
|
ret = call_hook_16( proc, id, code, wparam, lparam );
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* call_hook_32_to_32
|
|
*
|
|
* Convert hook params to/from Unicode and call hook procedure
|
|
*/
|
|
static LRESULT call_hook_32_to_32( HOOKPROC proc, INT id, INT code, WPARAM wparam, LPARAM lparam,
|
|
BOOL to_unicode )
|
|
{
|
|
if (id != WH_CBT || code != HCBT_CREATEWND) return proc( code, wparam, lparam );
|
|
|
|
if (to_unicode) /* ASCII to Unicode */
|
|
{
|
|
CBT_CREATEWNDA *cbtcwA = (CBT_CREATEWNDA *)lparam;
|
|
CBT_CREATEWNDW cbtcwW;
|
|
CREATESTRUCTW csW;
|
|
LRESULT ret;
|
|
|
|
cbtcwW.lpcs = &csW;
|
|
cbtcwW.hwndInsertAfter = cbtcwA->hwndInsertAfter;
|
|
csW = *(CREATESTRUCTW *)cbtcwA->lpcs;
|
|
|
|
if (HIWORD(cbtcwA->lpcs->lpszName))
|
|
csW.lpszName = HEAP_strdupAtoW( GetProcessHeap(), 0, cbtcwA->lpcs->lpszName );
|
|
if (HIWORD(cbtcwA->lpcs->lpszClass))
|
|
csW.lpszClass = HEAP_strdupAtoW( GetProcessHeap(), 0, cbtcwA->lpcs->lpszClass );
|
|
ret = proc( code, wparam, (LPARAM)&cbtcwW );
|
|
cbtcwA->hwndInsertAfter = cbtcwW.hwndInsertAfter;
|
|
if (HIWORD(csW.lpszName)) HeapFree( GetProcessHeap(), 0, (LPWSTR)csW.lpszName );
|
|
if (HIWORD(csW.lpszClass)) HeapFree( GetProcessHeap(), 0, (LPWSTR)csW.lpszClass );
|
|
return ret;
|
|
}
|
|
else /* Unicode to ASCII */
|
|
{
|
|
CBT_CREATEWNDW *cbtcwW = (CBT_CREATEWNDW *)lparam;
|
|
CBT_CREATEWNDA cbtcwA;
|
|
CREATESTRUCTA csA;
|
|
LRESULT ret;
|
|
|
|
cbtcwA.lpcs = &csA;
|
|
cbtcwA.hwndInsertAfter = cbtcwW->hwndInsertAfter;
|
|
csA = *(CREATESTRUCTA *)cbtcwW->lpcs;
|
|
|
|
if (HIWORD(cbtcwW->lpcs->lpszName))
|
|
csA.lpszName = HEAP_strdupWtoA( GetProcessHeap(), 0, cbtcwW->lpcs->lpszName );
|
|
if (HIWORD(cbtcwW->lpcs->lpszClass))
|
|
csA.lpszClass = HEAP_strdupWtoA( GetProcessHeap(), 0, cbtcwW->lpcs->lpszClass );
|
|
ret = proc( code, wparam, (LPARAM)&cbtcwA );
|
|
cbtcwW->hwndInsertAfter = cbtcwA.hwndInsertAfter;
|
|
if (HIWORD(csA.lpszName)) HeapFree( GetProcessHeap(), 0, (LPSTR)csA.lpszName );
|
|
if (HIWORD(csA.lpszClass)) HeapFree( GetProcessHeap(), 0, (LPSTR)csA.lpszClass );
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* call_hook
|
|
*
|
|
* Call a hook procedure.
|
|
*/
|
|
inline static LRESULT call_hook( HOOKDATA *data, INT fromtype, INT code,
|
|
WPARAM wparam, LPARAM lparam )
|
|
{
|
|
INT type = (data->flags & HOOK_MAPTYPE);
|
|
LRESULT ret;
|
|
|
|
/* Suspend window structure locks before calling user code */
|
|
int iWndsLocks = WIN_SuspendWndsLock();
|
|
|
|
if (type == HOOK_WIN16)
|
|
{
|
|
if (fromtype == HOOK_WIN16) /* 16->16 */
|
|
ret = call_hook_16( (HOOKPROC16)data->proc, data->id, code, wparam, lparam );
|
|
else /* 32->16 */
|
|
ret = call_hook_32_to_16( (HOOKPROC16)data->proc, data->id, code, wparam,
|
|
lparam, (type == HOOK_WIN32W) );
|
|
}
|
|
else if (fromtype == HOOK_WIN16) /* 16->32 */
|
|
ret = call_hook_16_to_32( data->proc, data->id, code, wparam,
|
|
lparam, (type == HOOK_WIN32W) );
|
|
else /* 32->32, check unicode */
|
|
{
|
|
if (type == fromtype)
|
|
ret = data->proc( code, wparam, lparam );
|
|
else
|
|
ret = call_hook_32_to_32( data->proc, data->id, code, wparam,
|
|
lparam, (type == HOOK_WIN32W) );
|
|
}
|
|
WIN_RestoreWndsLock(iWndsLocks);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_GetNextHook
|
|
*
|
|
* Get the next hook of a given hook.
|
|
*/
|
|
static HANDLE16 HOOK_GetNextHook( HANDLE16 hook )
|
|
{
|
|
HOOKDATA *data = (HOOKDATA *)USER_HEAP_LIN_ADDR( hook );
|
|
|
|
if (!data || !hook) return 0;
|
|
if (data->next) return data->next;
|
|
if (!data->ownerQueue) return 0; /* Already system hook */
|
|
|
|
/* Now start enumerating the system hooks */
|
|
return HOOK_systemHooks[data->id - WH_MINHOOK];
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_GetHook
|
|
*
|
|
* Get the first hook for a given type.
|
|
*/
|
|
static HANDLE16 HOOK_GetHook( INT16 id )
|
|
{
|
|
MESSAGEQUEUE *queue;
|
|
HANDLE16 hook = 0;
|
|
|
|
if ((queue = QUEUE_Current()) != NULL)
|
|
hook = queue->hooks[id - WH_MINHOOK];
|
|
if (!hook) hook = HOOK_systemHooks[id - WH_MINHOOK];
|
|
return hook;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_SetHook
|
|
*
|
|
* Install a given hook.
|
|
*/
|
|
static HHOOK HOOK_SetHook( INT16 id, LPVOID proc, INT type,
|
|
HMODULE16 hModule, DWORD dwThreadId )
|
|
{
|
|
HOOKDATA *data;
|
|
HANDLE16 handle;
|
|
HQUEUE16 hQueue = 0;
|
|
|
|
if ((id < WH_MINHOOK) || (id > WH_MAXHOOK)) return 0;
|
|
|
|
TRACE("Setting hook %d: %08x %04x %08lx\n",
|
|
id, (UINT)proc, hModule, dwThreadId );
|
|
|
|
/* Create task queue if none present */
|
|
InitThreadInput16( 0, 0 );
|
|
|
|
if (id == WH_JOURNALPLAYBACK) EnableHardwareInput16(FALSE);
|
|
|
|
if (dwThreadId) /* Task-specific hook */
|
|
{
|
|
if ((id == WH_JOURNALRECORD) || (id == WH_JOURNALPLAYBACK) ||
|
|
(id == WH_SYSMSGFILTER)) return 0; /* System-only hooks */
|
|
if (!(hQueue = GetThreadQueue16( dwThreadId )))
|
|
return 0;
|
|
}
|
|
|
|
/* Create the hook structure */
|
|
|
|
if (!(handle = USER_HEAP_ALLOC( sizeof(HOOKDATA) ))) return 0;
|
|
data = (HOOKDATA *) USER_HEAP_LIN_ADDR( handle );
|
|
data->proc = proc;
|
|
data->id = id;
|
|
data->ownerQueue = hQueue;
|
|
data->ownerModule = hModule;
|
|
data->flags = type;
|
|
|
|
/* Insert it in the correct linked list */
|
|
|
|
if (hQueue)
|
|
{
|
|
MESSAGEQUEUE *queue = (MESSAGEQUEUE *)QUEUE_Lock( hQueue );
|
|
data->next = queue->hooks[id - WH_MINHOOK];
|
|
queue->hooks[id - WH_MINHOOK] = handle;
|
|
QUEUE_Unlock( queue );
|
|
}
|
|
else
|
|
{
|
|
data->next = HOOK_systemHooks[id - WH_MINHOOK];
|
|
HOOK_systemHooks[id - WH_MINHOOK] = handle;
|
|
}
|
|
TRACE("Setting hook %d: ret=%04x [next=%04x]\n",
|
|
id, handle, data->next );
|
|
|
|
return (HHOOK)( handle? MAKELONG( handle, HOOK_MAGIC ) : 0 );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_RemoveHook
|
|
*
|
|
* Remove a hook from the list.
|
|
*/
|
|
static BOOL HOOK_RemoveHook( HANDLE16 hook )
|
|
{
|
|
HOOKDATA *data;
|
|
HANDLE16 *prevHook;
|
|
|
|
TRACE("Removing hook %04x\n", hook );
|
|
|
|
if (!(data = (HOOKDATA *)USER_HEAP_LIN_ADDR(hook))) return FALSE;
|
|
if (data->flags & HOOK_INUSE)
|
|
{
|
|
/* Mark it for deletion later on */
|
|
WARN("Hook still running, deletion delayed\n" );
|
|
data->proc = (HOOKPROC)0;
|
|
return TRUE;
|
|
}
|
|
|
|
if (data->id == WH_JOURNALPLAYBACK) EnableHardwareInput16(TRUE);
|
|
|
|
/* Remove it from the linked list */
|
|
|
|
if (data->ownerQueue)
|
|
{
|
|
MESSAGEQUEUE *queue = (MESSAGEQUEUE *)QUEUE_Lock( data->ownerQueue );
|
|
if (!queue) return FALSE;
|
|
prevHook = &queue->hooks[data->id - WH_MINHOOK];
|
|
QUEUE_Unlock( queue );
|
|
}
|
|
else prevHook = &HOOK_systemHooks[data->id - WH_MINHOOK];
|
|
|
|
while (*prevHook && *prevHook != hook)
|
|
prevHook = &((HOOKDATA *)USER_HEAP_LIN_ADDR(*prevHook))->next;
|
|
|
|
if (!*prevHook) return FALSE;
|
|
*prevHook = data->next;
|
|
|
|
USER_HEAP_FREE( hook );
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_FindValidHook
|
|
*/
|
|
static HANDLE16 HOOK_FindValidHook( HANDLE16 hook )
|
|
{
|
|
HOOKDATA *data;
|
|
|
|
for (;;)
|
|
{
|
|
if (!(data = (HOOKDATA *)USER_HEAP_LIN_ADDR(hook))) return 0;
|
|
if (data->proc) return hook;
|
|
hook = data->next;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* HOOK_CallHook
|
|
*
|
|
* Call a hook procedure.
|
|
*/
|
|
static LRESULT HOOK_CallHook( HANDLE16 hook, INT fromtype, INT code,
|
|
WPARAM wParam, LPARAM lParam )
|
|
{
|
|
MESSAGEQUEUE *queue;
|
|
HANDLE16 prevHook;
|
|
HOOKDATA *data = (HOOKDATA *)USER_HEAP_LIN_ADDR(hook);
|
|
LRESULT ret;
|
|
|
|
if (!(queue = QUEUE_Current())) return 0;
|
|
prevHook = queue->hCurHook;
|
|
queue->hCurHook = hook;
|
|
|
|
TRACE("Calling hook %04x: %d %08x %08lx\n", hook, code, wParam, lParam );
|
|
|
|
data->flags |= HOOK_INUSE;
|
|
ret = call_hook( data, fromtype, code, wParam, lParam );
|
|
data->flags &= ~HOOK_INUSE;
|
|
|
|
TRACE("Ret hook %04x = %08lx\n", hook, ret );
|
|
|
|
queue->hCurHook = prevHook;
|
|
if (!data->proc) HOOK_RemoveHook( hook );
|
|
return ret;
|
|
}
|
|
|
|
/***********************************************************************
|
|
* Exported Functions & APIs
|
|
*/
|
|
|
|
/***********************************************************************
|
|
* HOOK_IsHooked
|
|
*
|
|
* Replacement for calling HOOK_GetHook from other modules.
|
|
*/
|
|
BOOL HOOK_IsHooked( INT16 id )
|
|
{
|
|
return HOOK_GetHook( id ) != 0;
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_CallHooks16
|
|
*
|
|
* Call a hook chain.
|
|
*/
|
|
LRESULT HOOK_CallHooks16( INT16 id, INT16 code, WPARAM16 wParam,
|
|
LPARAM lParam )
|
|
{
|
|
HANDLE16 hook;
|
|
|
|
if (!(hook = HOOK_GetHook( id ))) return 0;
|
|
if (!(hook = HOOK_FindValidHook(hook))) return 0;
|
|
return HOOK_CallHook( hook, HOOK_WIN16, code, wParam, lParam );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* HOOK_CallHooksA
|
|
*
|
|
* Call a hook chain.
|
|
*/
|
|
LRESULT HOOK_CallHooksA( INT id, INT code, WPARAM wParam,
|
|
LPARAM lParam )
|
|
{
|
|
HANDLE16 hook;
|
|
|
|
if (!(hook = HOOK_GetHook( id ))) return 0;
|
|
if (!(hook = HOOK_FindValidHook(hook))) return 0;
|
|
return HOOK_CallHook( hook, HOOK_WIN32A, code, wParam, lParam );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* HOOK_CallHooksW
|
|
*
|
|
* Call a hook chain.
|
|
*/
|
|
LRESULT HOOK_CallHooksW( INT id, INT code, WPARAM wParam,
|
|
LPARAM lParam )
|
|
{
|
|
HANDLE16 hook;
|
|
|
|
if (!(hook = HOOK_GetHook( id ))) return 0;
|
|
if (!(hook = HOOK_FindValidHook(hook))) return 0;
|
|
return HOOK_CallHook( hook, HOOK_WIN32W, code, wParam,
|
|
lParam );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* HOOK_FreeModuleHooks
|
|
*/
|
|
void HOOK_FreeModuleHooks( HMODULE16 hModule )
|
|
{
|
|
/* remove all system hooks registered by this module */
|
|
|
|
HOOKDATA* hptr;
|
|
HHOOK hook, next;
|
|
int id;
|
|
|
|
for( id = WH_MINHOOK; id <= WH_MAXHOOK; id++ )
|
|
{
|
|
hook = HOOK_systemHooks[id - WH_MINHOOK];
|
|
while( hook )
|
|
if( (hptr = (HOOKDATA *)USER_HEAP_LIN_ADDR(hook)) )
|
|
{
|
|
next = hptr->next;
|
|
if( hptr->ownerModule == hModule )
|
|
{
|
|
hptr->flags &= HOOK_MAPTYPE;
|
|
HOOK_RemoveHook(hook);
|
|
}
|
|
hook = next;
|
|
}
|
|
else hook = 0;
|
|
}
|
|
}
|
|
|
|
/***********************************************************************
|
|
* HOOK_FreeQueueHooks
|
|
*/
|
|
void HOOK_FreeQueueHooks(void)
|
|
{
|
|
/* remove all hooks registered by the current queue */
|
|
|
|
HOOKDATA* hptr = NULL;
|
|
HHOOK hook, next;
|
|
int id;
|
|
|
|
for( id = WH_MINHOOK; id <= WH_MAXHOOK; id++ )
|
|
{
|
|
hook = HOOK_GetHook( id );
|
|
while( hook )
|
|
{
|
|
next = HOOK_GetNextHook(hook);
|
|
|
|
hptr = (HOOKDATA *)USER_HEAP_LIN_ADDR(hook);
|
|
if( hptr && hptr->ownerQueue )
|
|
{
|
|
hptr->flags &= HOOK_MAPTYPE;
|
|
HOOK_RemoveHook(hook);
|
|
}
|
|
hook = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetWindowsHook (USER.121)
|
|
*/
|
|
FARPROC16 WINAPI SetWindowsHook16( INT16 id, HOOKPROC16 proc )
|
|
{
|
|
HINSTANCE16 hInst = FarGetOwner16( HIWORD(proc) );
|
|
|
|
/* WH_MSGFILTER is the only task-specific hook for SetWindowsHook() */
|
|
HTASK16 hTask = (id == WH_MSGFILTER) ? GetCurrentTask() : 0;
|
|
|
|
return (FARPROC16)SetWindowsHookEx16( id, proc, hInst, hTask );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* SetWindowsHookA (USER32.@)
|
|
*/
|
|
HHOOK WINAPI SetWindowsHookA( INT id, HOOKPROC proc )
|
|
{
|
|
return SetWindowsHookExA( id, proc, 0, GetCurrentThreadId() );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* SetWindowsHookW (USER32.@)
|
|
*/
|
|
HHOOK WINAPI SetWindowsHookW( INT id, HOOKPROC proc )
|
|
{
|
|
return SetWindowsHookExW( id, proc, 0, GetCurrentThreadId() );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* SetWindowsHookEx (USER.291)
|
|
* SetWindowsHookEx16 (USER32.@)
|
|
*/
|
|
HHOOK WINAPI SetWindowsHookEx16( INT16 id, HOOKPROC16 proc, HINSTANCE16 hInst,
|
|
HTASK16 hTask )
|
|
{
|
|
if (id == WH_DEBUG)
|
|
{
|
|
FIXME("WH_DEBUG is broken in 16-bit Windows.\n");
|
|
return 0;
|
|
}
|
|
return HOOK_SetHook( id, proc, HOOK_WIN16, GetExePtr(hInst), (DWORD)hTask );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* SetWindowsHookExA (USER32.@)
|
|
*/
|
|
HHOOK WINAPI SetWindowsHookExA( INT id, HOOKPROC proc, HINSTANCE hInst,
|
|
DWORD dwThreadId )
|
|
{
|
|
return HOOK_SetHook( id, proc, HOOK_WIN32A, MapHModuleLS(hInst), dwThreadId );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* SetWindowsHookExW (USER32.@)
|
|
*/
|
|
HHOOK WINAPI SetWindowsHookExW( INT id, HOOKPROC proc, HINSTANCE hInst,
|
|
DWORD dwThreadId )
|
|
{
|
|
return HOOK_SetHook( id, proc, HOOK_WIN32W, MapHModuleLS(hInst), dwThreadId );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* UnhookWindowsHook (USER.234)
|
|
*/
|
|
BOOL16 WINAPI UnhookWindowsHook16( INT16 id, HOOKPROC16 proc )
|
|
{
|
|
return UnhookWindowsHook( id, (HOOKPROC)proc );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* UnhookWindowsHook (USER32.@)
|
|
*/
|
|
BOOL WINAPI UnhookWindowsHook( INT id, HOOKPROC proc )
|
|
{
|
|
HANDLE16 hook = HOOK_GetHook( id );
|
|
|
|
TRACE("%d %08lx\n", id, (DWORD)proc );
|
|
|
|
while (hook)
|
|
{
|
|
HOOKDATA *data = (HOOKDATA *)USER_HEAP_LIN_ADDR(hook);
|
|
if (data->proc == proc) break;
|
|
hook = HOOK_GetNextHook( hook );
|
|
}
|
|
if (!hook) return FALSE;
|
|
return HOOK_RemoveHook( hook );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* UnhookWindowsHookEx (USER.292)
|
|
*/
|
|
BOOL16 WINAPI UnhookWindowsHookEx16( HHOOK hhook )
|
|
{
|
|
return UnhookWindowsHookEx( hhook );
|
|
}
|
|
|
|
/***********************************************************************
|
|
* UnhookWindowsHookEx (USER32.@)
|
|
*/
|
|
BOOL WINAPI UnhookWindowsHookEx( HHOOK hhook )
|
|
{
|
|
if (HIWORD(hhook) != HOOK_MAGIC) return FALSE; /* Not a new format hook */
|
|
return HOOK_RemoveHook( LOWORD(hhook) );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* CallNextHookEx (USER.293)
|
|
* CallNextHookEx16 (USER32.@)
|
|
*
|
|
* I wouldn't have separated this into 16 and 32 bit versions, but I
|
|
* need a way to figure out if I need to do a mapping or not.
|
|
*/
|
|
LRESULT WINAPI CallNextHookEx16( HHOOK hhook, INT16 code, WPARAM16 wParam,
|
|
LPARAM lParam )
|
|
{
|
|
HANDLE16 next;
|
|
|
|
if (HIWORD(hhook) != HOOK_MAGIC) return 0; /* Not a new format hook */
|
|
if (!(next = HOOK_GetNextHook( LOWORD(hhook) ))) return 0;
|
|
|
|
return HOOK_CallHook( next, HOOK_WIN16, code, wParam, lParam );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* CallNextHookEx (USER32.@)
|
|
*
|
|
* There aren't ANSI and UNICODE versions of this.
|
|
*/
|
|
LRESULT WINAPI CallNextHookEx( HHOOK hhook, INT code, WPARAM wParam,
|
|
LPARAM lParam )
|
|
{
|
|
HANDLE16 next;
|
|
INT fromtype; /* figure out Ansi/Unicode */
|
|
HOOKDATA *oldhook;
|
|
|
|
if (HIWORD(hhook) != HOOK_MAGIC) return 0; /* Not a new format hook */
|
|
if (!(next = HOOK_GetNextHook( LOWORD(hhook) ))) return 0;
|
|
|
|
oldhook = (HOOKDATA *)USER_HEAP_LIN_ADDR( LOWORD(hhook) );
|
|
fromtype = oldhook->flags & HOOK_MAPTYPE;
|
|
|
|
if (fromtype == HOOK_WIN16)
|
|
ERR("called from 16bit hook!\n");
|
|
|
|
return HOOK_CallHook( next, fromtype, code, wParam, lParam );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* DefHookProc (USER.235)
|
|
*/
|
|
LRESULT WINAPI DefHookProc16( INT16 code, WPARAM16 wParam, LPARAM lParam,
|
|
HHOOK *hhook )
|
|
{
|
|
/* Note: the *hhook parameter is never used, since we rely on the
|
|
* current hook value from the task queue to find the next hook. */
|
|
MESSAGEQUEUE *queue;
|
|
|
|
if (!(queue = QUEUE_Current())) return 0;
|
|
return CallNextHookEx16( queue->hCurHook, code, wParam, lParam );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* CallMsgFilter (USER.123)
|
|
*/
|
|
BOOL16 WINAPI CallMsgFilter16( SEGPTR msg, INT16 code )
|
|
{
|
|
if (GetSysModalWindow16()) return FALSE;
|
|
if (HOOK_CallHooks16( WH_SYSMSGFILTER, code, 0, (LPARAM)msg )) return TRUE;
|
|
return HOOK_CallHooks16( WH_MSGFILTER, code, 0, (LPARAM)msg );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* CallMsgFilter32 (USER.823)
|
|
*/
|
|
BOOL16 WINAPI CallMsgFilter32_16( SEGPTR msg16_32, INT16 code, BOOL16 wHaveParamHigh )
|
|
{
|
|
MSG32_16 *lpmsg16_32 = MapSL(msg16_32);
|
|
|
|
if (wHaveParamHigh == FALSE)
|
|
{
|
|
lpmsg16_32->wParamHigh = 0;
|
|
/* WARNING: msg16_32->msg has to be the first variable in the struct */
|
|
return CallMsgFilter16(msg16_32, code);
|
|
}
|
|
else
|
|
{
|
|
MSG msg32;
|
|
BOOL16 ret;
|
|
|
|
msg32.hwnd = WIN_Handle32( lpmsg16_32->msg.hwnd );
|
|
msg32.message = lpmsg16_32->msg.message;
|
|
msg32.wParam = MAKELONG(lpmsg16_32->msg.wParam, lpmsg16_32->wParamHigh);
|
|
msg32.lParam = lpmsg16_32->msg.lParam;
|
|
msg32.time = lpmsg16_32->msg.time;
|
|
msg32.pt.x = lpmsg16_32->msg.pt.x;
|
|
msg32.pt.y = lpmsg16_32->msg.pt.y;
|
|
|
|
ret = (BOOL16)CallMsgFilterA(&msg32, (INT)code);
|
|
|
|
lpmsg16_32->msg.hwnd = WIN_Handle16( msg32.hwnd );
|
|
lpmsg16_32->msg.message = msg32.message;
|
|
lpmsg16_32->msg.wParam = LOWORD(msg32.wParam);
|
|
lpmsg16_32->msg.lParam = msg32.lParam;
|
|
lpmsg16_32->msg.time = msg32.time;
|
|
lpmsg16_32->msg.pt.x = msg32.pt.x;
|
|
lpmsg16_32->msg.pt.y = msg32.pt.y;
|
|
lpmsg16_32->wParamHigh = HIWORD(msg32.wParam);
|
|
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* CallMsgFilterA (USER32.@)
|
|
*
|
|
* FIXME: There are ANSI and UNICODE versions of this, plus an unspecified
|
|
* version, plus USER (the 16bit one) has a CallMsgFilter32 function.
|
|
*/
|
|
BOOL WINAPI CallMsgFilterA( LPMSG msg, INT code )
|
|
{
|
|
if (GetSysModalWindow16()) return FALSE; /* ??? */
|
|
if (HOOK_CallHooksA( WH_SYSMSGFILTER, code, 0, (LPARAM)msg ))
|
|
return TRUE;
|
|
return HOOK_CallHooksA( WH_MSGFILTER, code, 0, (LPARAM)msg );
|
|
}
|
|
|
|
|
|
/***********************************************************************
|
|
* CallMsgFilterW (USER32.@)
|
|
*/
|
|
BOOL WINAPI CallMsgFilterW( LPMSG msg, INT code )
|
|
{
|
|
if (GetSysModalWindow16()) return FALSE; /* ??? */
|
|
if (HOOK_CallHooksW( WH_SYSMSGFILTER, code, 0, (LPARAM)msg ))
|
|
return TRUE;
|
|
return HOOK_CallHooksW( WH_MSGFILTER, code, 0, (LPARAM)msg );
|
|
}
|
|
|