/*
 * Windows hook functions
 *
 * Copyright 1994, 1995 Alexandre Julliard
 *                 1996 Andrew Lewycky
 *
 * Based on investigations by Alex Korobka
 */

/*
 * 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' */
#define HHOOK_32(h) ((HHOOK)(h ? MAKELONG(h, HOOK_MAGIC) : 0))
#define HHOOK_16(h) ((HANDLE16)((HIWORD(h) == HOOK_MAGIC) ? LOWORD(h) : 0))

  /* 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 HHOOK HOOK_GetNextHook( HHOOK hook )
{
    HOOKDATA *data = (HOOKDATA *)USER_HEAP_LIN_ADDR(HHOOK_16(hook));

    if (!data || !hook) return 0;
    if (data->next) return HHOOK_32(data->next);
    if (!data->ownerQueue) return 0;  /* Already system hook */

    /* Now start enumerating the system hooks */
    return HHOOK_32(HOOK_systemHooks[data->id - WH_MINHOOK]);
}


/***********************************************************************
 *           HOOK_GetHook
 *
 * Get the first hook for a given type.
 */
static HHOOK HOOK_GetHook( INT16 id )
{
    MESSAGEQUEUE *queue;
    HANDLE16 handle = 0;

    if ((queue = QUEUE_Current()) != NULL)
        handle = queue->hooks[id - WH_MINHOOK];
    if (!handle) handle = HOOK_systemHooks[id - WH_MINHOOK];
    return HHOOK_32(handle);
}


/***********************************************************************
 *           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_32(handle);
}


/***********************************************************************
 *           HOOK_RemoveHook
 *
 * Remove a hook from the list.
 */
static BOOL HOOK_RemoveHook( HHOOK hook )
{
    HOOKDATA *data;
    HANDLE16 *prevHandle;

    TRACE("Removing hook %04x\n", hook );

    if (!(data = (HOOKDATA *)USER_HEAP_LIN_ADDR(HHOOK_16(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;
        prevHandle = &queue->hooks[data->id - WH_MINHOOK];
        QUEUE_Unlock( queue );
    }
    else prevHandle = &HOOK_systemHooks[data->id - WH_MINHOOK];

    while (*prevHandle && *prevHandle != HHOOK_16(hook))
        prevHandle = &((HOOKDATA *)USER_HEAP_LIN_ADDR(*prevHandle))->next;

    if (!*prevHandle) return FALSE;
    *prevHandle = data->next;

    USER_HEAP_FREE(HHOOK_16(hook));
    return TRUE;
}


/***********************************************************************
 *           HOOK_FindValidHook
 */
static HHOOK HOOK_FindValidHook( HHOOK hook )
{
    HOOKDATA *data;

    for (;;)
    {
	if (!(data = (HOOKDATA *)USER_HEAP_LIN_ADDR(HHOOK_16(hook)))) return 0;
	if (data->proc) return hook;
	hook = HHOOK_32(data->next);
    }
}

/***********************************************************************
 *           HOOK_CallHook
 *
 * Call a hook procedure.
 */
static LRESULT HOOK_CallHook( HHOOK hook, INT fromtype, INT code,
                              WPARAM wParam, LPARAM lParam )
{
    MESSAGEQUEUE *queue;
    HANDLE16 prevHandle;
    HOOKDATA *data = (HOOKDATA *)USER_HEAP_LIN_ADDR(HHOOK_16(hook));
    LRESULT ret;

    if (!(queue = QUEUE_Current())) return 0;
    prevHandle = queue->hCurHook;
    queue->hCurHook = HHOOK_16(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 = prevHandle;
    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 )
{
    HHOOK 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 )
{
    HHOOK 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 )
{
    HHOOK 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;
  HANDLE16      handle, next;
  int           id;

  for( id = WH_MINHOOK; id <= WH_MAXHOOK; id++ )
    {
       handle = HOOK_systemHooks[id - WH_MINHOOK];
       while( handle )
          if( (hptr = (HOOKDATA *)USER_HEAP_LIN_ADDR(handle)) )
	    {
	      next = hptr->next;
	      if( hptr->ownerModule == hModule )
                {
                  hptr->flags &= HOOK_MAPTYPE;
                  HOOK_RemoveHook(HHOOK_32(handle));
                }
	      handle = next;
	    }
	  else handle = 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(HHOOK_16(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 )
{
    HHOOK hook = HOOK_GetHook( id );

    TRACE("%d %08lx\n", id, (DWORD)proc );

    while (hook)
    {
        HOOKDATA *data = (HOOKDATA *)USER_HEAP_LIN_ADDR(HHOOK_16(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 HOOK_RemoveHook(hhook);
}

/***********************************************************************
 *		UnhookWindowsHookEx (USER32.@)
 */
BOOL WINAPI UnhookWindowsHookEx( HHOOK hhook )
{
    return HOOK_RemoveHook(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 )
{
    HHOOK next;

    if (!(next = HOOK_GetNextHook(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 )
{
    HHOOK next;
    INT fromtype;	/* figure out Ansi/Unicode */
    HOOKDATA *oldhook;

    if (!(next = HOOK_GetNextHook(hhook))) return 0;

    oldhook = (HOOKDATA *)USER_HEAP_LIN_ADDR(HHOOK_16(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(HHOOK_32(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 );
}

