Skip to content
Snippets Groups Projects
Forked from wine / wine
169013 commits behind the upstream repository.
queue.c 47.25 KiB
/* * Message queues related functions
 *
 * Copyright 1993, 1994 Alexandre Julliard
 */

#include <string.h>
#include <signal.h>
#include <assert.h>
#include "windef.h"
#include "wingdi.h"
#include "winerror.h"
#include "wine/winbase16.h"
#include "wine/winuser16.h"
#include "queue.h"
#include "win.h"
#include "hook.h"
#include "thread.h"
#include "debugtools.h"
#include "server.h"
#include "spy.h"

DECLARE_DEBUG_CHANNEL(msg);
DECLARE_DEBUG_CHANNEL(sendmsg);

#define MAX_QUEUE_SIZE   120  /* Max. size of a message queue */

static HQUEUE16 hFirstQueue = 0;
static HQUEUE16 hExitingQueue = 0;
static HQUEUE16 hmemSysMsgQueue = 0;
static MESSAGEQUEUE *sysMsgQueue = NULL;
static PERQUEUEDATA *pQDataWin16 = NULL;  /* Global perQData for Win16 tasks */

static MESSAGEQUEUE *pMouseQueue = NULL;  /* Queue for last mouse message */
static MESSAGEQUEUE *pKbdQueue = NULL;    /* Queue for last kbd message */

HQUEUE16 hCursorQueue = 0;
HQUEUE16 hActiveQueue = 0;


/***********************************************************************
 *           PERQDATA_CreateInstance
 *
 * Creates an instance of a reference counted PERQUEUEDATA element
 * for the message queue. perQData is stored globally for 16 bit tasks.
 *
 * Note: We don't implement perQdata exactly the same way Windows does.
 * Each perQData element is reference counted since it may be potentially
 * shared by multiple message Queues (via AttachThreadInput).
 * We only store the current values for Active, Capture and focus windows
 * currently.
 */
PERQUEUEDATA * PERQDATA_CreateInstance( )
{
    PERQUEUEDATA *pQData;
    
    BOOL16 bIsWin16 = 0;
    
    TRACE_(msg)("()\n");

    /* Share a single instance of perQData for all 16 bit tasks */
    if ( ( bIsWin16 = THREAD_IsWin16( NtCurrentTeb() ) ) )
    {
        /* If previously allocated, just bump up ref count */
        if ( pQDataWin16 )
        {
            PERQDATA_Addref( pQDataWin16 );
            return pQDataWin16;
        }
    }
    /* Allocate PERQUEUEDATA from the system heap */
    if (!( pQData = (PERQUEUEDATA *) HeapAlloc( GetProcessHeap(), 0,
                                                    sizeof(PERQUEUEDATA) ) ))
        return 0;

    /* Initialize */
    pQData->hWndCapture = pQData->hWndFocus = pQData->hWndActive = 0;
    pQData->ulRefCount = 1;
    pQData->nCaptureHT = HTCLIENT;

    /* Note: We have an independent critical section for the per queue data
     * since this may be shared by different threads. see AttachThreadInput()
     */
    InitializeCriticalSection( &pQData->cSection );
    /* FIXME: not all per queue data critical sections should be global */
    MakeCriticalSectionGlobal( &pQData->cSection );

    /* Save perQData globally for 16 bit tasks */
    if ( bIsWin16 )
        pQDataWin16 = pQData;
        
    return pQData;
}


/***********************************************************************
 *           PERQDATA_Addref
 *
 * Increment reference count for the PERQUEUEDATA instance
 * Returns reference count for debugging purposes
 */
ULONG PERQDATA_Addref( PERQUEUEDATA *pQData )
{
    assert(pQData != 0 );
    TRACE_(msg)("(): current refcount %lu ...\n", pQData->ulRefCount);

    EnterCriticalSection( &pQData->cSection );
    ++pQData->ulRefCount;
    LeaveCriticalSection( &pQData->cSection );

    return pQData->ulRefCount;
}


/***********************************************************************
 *           PERQDATA_Release
 *
 * Release a reference to a PERQUEUEDATA instance.
 * Destroy the instance if no more references exist
 * Returns reference count for debugging purposes
 */
ULONG PERQDATA_Release( PERQUEUEDATA *pQData )
{
    assert(pQData != 0 );
    TRACE_(msg)("(): current refcount %lu ...\n",
          (LONG)pQData->ulRefCount );

    EnterCriticalSection( &pQData->cSection );
    if ( --pQData->ulRefCount == 0 )
    {
        LeaveCriticalSection( &pQData->cSection );
        DeleteCriticalSection( &pQData->cSection );

        TRACE_(msg)("(): deleting PERQUEUEDATA instance ...\n" );

        /* Deleting our global 16 bit perQData? */
        if ( pQData == pQDataWin16 )
            pQDataWin16 = 0;
            
        /* Free the PERQUEUEDATA instance */
        HeapFree( GetProcessHeap(), 0, pQData );

        return 0;
    }
    LeaveCriticalSection( &pQData->cSection );

    return pQData->ulRefCount;
}


/***********************************************************************
 *           PERQDATA_GetFocusWnd
 *
 * Get the focus hwnd member in a threadsafe manner
 */
HWND PERQDATA_GetFocusWnd( PERQUEUEDATA *pQData )
{
    HWND hWndFocus;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    hWndFocus = pQData->hWndFocus;
    LeaveCriticalSection( &pQData->cSection );

    return hWndFocus;
}


/***********************************************************************
 *           PERQDATA_SetFocusWnd
 *
 * Set the focus hwnd member in a threadsafe manner
 */
HWND PERQDATA_SetFocusWnd( PERQUEUEDATA *pQData, HWND hWndFocus )
{
    HWND hWndFocusPrv;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    hWndFocusPrv = pQData->hWndFocus;
    pQData->hWndFocus = hWndFocus;
    LeaveCriticalSection( &pQData->cSection );

    return hWndFocusPrv;
}


/***********************************************************************
 *           PERQDATA_GetActiveWnd
 *
 * Get the active hwnd member in a threadsafe manner
 */
HWND PERQDATA_GetActiveWnd( PERQUEUEDATA *pQData )
{
    HWND hWndActive;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    hWndActive = pQData->hWndActive;
    LeaveCriticalSection( &pQData->cSection );

    return hWndActive;
}


/***********************************************************************
 *           PERQDATA_SetActiveWnd
 *
 * Set the active focus hwnd member in a threadsafe manner
 */
HWND PERQDATA_SetActiveWnd( PERQUEUEDATA *pQData, HWND hWndActive )
{
    HWND hWndActivePrv;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    hWndActivePrv = pQData->hWndActive;
    pQData->hWndActive = hWndActive;
    LeaveCriticalSection( &pQData->cSection );

    return hWndActivePrv;
}


/***********************************************************************
 *           PERQDATA_GetCaptureWnd
 *
 * Get the capture hwnd member in a threadsafe manner
 */
HWND PERQDATA_GetCaptureWnd( PERQUEUEDATA *pQData )
{
    HWND hWndCapture;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    hWndCapture = pQData->hWndCapture;
    LeaveCriticalSection( &pQData->cSection );

    return hWndCapture;
}


/***********************************************************************
 *           PERQDATA_SetCaptureWnd
 *
 * Set the capture hwnd member in a threadsafe manner
 */
HWND PERQDATA_SetCaptureWnd( PERQUEUEDATA *pQData, HWND hWndCapture )
{
    HWND hWndCapturePrv;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    hWndCapturePrv = pQData->hWndCapture;
    pQData->hWndCapture = hWndCapture;
    LeaveCriticalSection( &pQData->cSection );

    return hWndCapturePrv;
}


/***********************************************************************
 *           PERQDATA_GetCaptureInfo
 *
 * Get the capture info member in a threadsafe manner
 */
INT16 PERQDATA_GetCaptureInfo( PERQUEUEDATA *pQData )
{
    INT16 nCaptureHT;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    nCaptureHT = pQData->nCaptureHT;
    LeaveCriticalSection( &pQData->cSection );

    return nCaptureHT;
}


/***********************************************************************
 *           PERQDATA_SetCaptureInfo
 *
 * Set the capture info member in a threadsafe manner
 */
INT16 PERQDATA_SetCaptureInfo( PERQUEUEDATA *pQData, INT16 nCaptureHT )
{
    INT16 nCaptureHTPrv;
    assert(pQData != 0 );

    EnterCriticalSection( &pQData->cSection );
    nCaptureHTPrv = pQData->nCaptureHT;
    pQData->nCaptureHT = nCaptureHT;
    LeaveCriticalSection( &pQData->cSection );

    return nCaptureHTPrv;
}


/***********************************************************************
 *	     QUEUE_Lock
 *
 * Function for getting a 32 bit pointer on queue structure. For thread
 * safeness programmers should use this function instead of GlobalLock to
 * retrieve a pointer on the structure. QUEUE_Unlock should also be called
 * when access to the queue structure is not required anymore.
 */
MESSAGEQUEUE *QUEUE_Lock( HQUEUE16 hQueue )
{
    MESSAGEQUEUE *queue;

    HeapLock( GetProcessHeap() );  /* FIXME: a bit overkill */
    queue = GlobalLock16( hQueue );
    if ( !queue || (queue->magic != QUEUE_MAGIC) )
    {
        HeapUnlock( GetProcessHeap() );
        return NULL;
    }

    queue->lockCount++;
    HeapUnlock( GetProcessHeap() );
    return queue;
}


/***********************************************************************
 *	     QUEUE_Unlock
 *
 * Use with QUEUE_Lock to get a thread safe access to message queue
 * structure
 */
void QUEUE_Unlock( MESSAGEQUEUE *queue )
{
    if (queue)
    {
        HeapLock( GetProcessHeap() );  /* FIXME: a bit overkill */

        if ( --queue->lockCount == 0 )
        {
            DeleteCriticalSection ( &queue->cSection );
            if (queue->server_queue)
                CloseHandle( queue->server_queue );
            GlobalFree16( queue->self );
        }
    
        HeapUnlock( GetProcessHeap() );
    }
}


/***********************************************************************
 *	     QUEUE_DumpQueue
 */
void QUEUE_DumpQueue( HQUEUE16 hQueue )
{
    MESSAGEQUEUE *pq; 

    if (!(pq = QUEUE_Lock( hQueue )) )
    {
        WARN_(msg)("%04x is not a queue handle\n", hQueue );
        return;
    }

    EnterCriticalSection( &pq->cSection );

    DPRINTF( "next: %12.4x  Intertask SendMessage:\n"
             "thread: %10p  ----------------------\n"
             "firstMsg: %8p   smWaiting:     %10p\n"
             "lastMsg:  %8p   smPending:     %10p\n"
             "msgCount: %8.4x   smProcessing:  %10p\n"
             "lockCount: %7.4x\n"
             "paints: %10.4x\n"
             "timers: %10.4x\n"
             "wakeBits: %8.4x\n"
             "wakeMask: %8.4x\n"
             "hCurHook: %8.4x\n",
             pq->next, pq->teb, pq->firstMsg, pq->smWaiting, pq->lastMsg,
             pq->smPending, pq->msgCount, pq->smProcessing,
             (unsigned)pq->lockCount, pq->wPaintCount, pq->wTimerCount,
             pq->wakeBits, pq->wakeMask, pq->hCurHook);

    LeaveCriticalSection( &pq->cSection );

    QUEUE_Unlock( pq );
}


/***********************************************************************
 *	     QUEUE_WalkQueues
 */
void QUEUE_WalkQueues(void)
{
    char module[10];
    HQUEUE16 hQueue = hFirstQueue;

    DPRINTF( "Queue Msgs Thread   Task Module\n" );
    while (hQueue)
    {
        MESSAGEQUEUE *queue = QUEUE_Lock( hQueue );
        if (!queue)
        {
            WARN_(msg)("Bad queue handle %04x\n", hQueue );
            return;
        }
        if (!GetModuleName16( queue->teb->htask16, module, sizeof(module )))
            strcpy( module, "???" );
        DPRINTF( "%04x %4d %p %04x %s\n", hQueue,queue->msgCount,
                 queue->teb, queue->teb->htask16, module );
        hQueue = queue->next;
        QUEUE_Unlock( queue );
    }
    DPRINTF( "\n" );
}


/***********************************************************************
 *	     QUEUE_IsExitingQueue
 */
BOOL QUEUE_IsExitingQueue( HQUEUE16 hQueue )
{
    return (hExitingQueue && (hQueue == hExitingQueue));
}


/***********************************************************************
 *	     QUEUE_SetExitingQueue
 */
void QUEUE_SetExitingQueue( HQUEUE16 hQueue )
{
    hExitingQueue = hQueue;
}


/***********************************************************************
 *           QUEUE_CreateMsgQueue
 *
 * Creates a message queue. Doesn't link it into queue list!
 */
static HQUEUE16 QUEUE_CreateMsgQueue( BOOL16 bCreatePerQData )
{
    HQUEUE16 hQueue;
    HANDLE handle;
    MESSAGEQUEUE * msgQueue;

    TRACE_(msg)("(): Creating message queue...\n");

    if (!(hQueue = GlobalAlloc16( GMEM_FIXED | GMEM_ZEROINIT,
                                  sizeof(MESSAGEQUEUE) )))
        return 0;

    msgQueue = (MESSAGEQUEUE *) GlobalLock16( hQueue );
    if ( !msgQueue )
        return 0;

    SERVER_START_REQ( get_msg_queue )
    {
        SERVER_CALL_ERR();
        handle = req->handle;
    }
    SERVER_END_REQ;
    if (!handle)
    {
        ERR_(msg)("Cannot get thread queue");
        GlobalFree16( hQueue );
        return 0;
    }
    msgQueue->server_queue = handle;
    msgQueue->server_queue = ConvertToGlobalHandle( msgQueue->server_queue );

    msgQueue->self        = hQueue;
    msgQueue->wakeBits    = msgQueue->changeBits = 0;
    
    InitializeCriticalSection( &msgQueue->cSection );
    MakeCriticalSectionGlobal( &msgQueue->cSection );

    msgQueue->lockCount = 1;
    msgQueue->magic = QUEUE_MAGIC;
    
    /* Create and initialize our per queue data */
    msgQueue->pQData = bCreatePerQData ? PERQDATA_CreateInstance() : NULL;
    
    return hQueue;
}


/***********************************************************************
 *           QUEUE_FlushMessage
 * 
 * Try to reply to all pending sent messages on exit.
 */
static void QUEUE_FlushMessages( MESSAGEQUEUE *queue )
{
    SMSG *smsg;
    MESSAGEQUEUE *senderQ = 0;

    if( queue )
    {
        EnterCriticalSection( &queue->cSection );

        /* empty the list of pending SendMessage waiting to be received */
        while (queue->smPending)
        {
            smsg = QUEUE_RemoveSMSG( queue, SM_PENDING_LIST, 0);

            senderQ = QUEUE_Lock( smsg->hSrcQueue );
            if ( !senderQ )
                continue;

            /* return 0, to unblock other thread */
            smsg->lResult = 0;
            smsg->flags |= SMSG_HAVE_RESULT;
            QUEUE_SetWakeBit( senderQ, QS_SMRESULT);
            
            QUEUE_Unlock( senderQ );
        }

        QUEUE_ClearWakeBit( queue, QS_SENDMESSAGE );
        
        LeaveCriticalSection( &queue->cSection );
    }
}


/***********************************************************************
 *	     QUEUE_DeleteMsgQueue
 *
 * Unlinks and deletes a message queue.
 *
 * Note: We need to mask asynchronous events to make sure PostMessage works
 * even in the signal handler.
 */
BOOL QUEUE_DeleteMsgQueue( HQUEUE16 hQueue )
{
    MESSAGEQUEUE * msgQueue = QUEUE_Lock(hQueue);
    HQUEUE16 *pPrev;

    TRACE_(msg)("(): Deleting message queue %04x\n", hQueue);

    if (!hQueue || !msgQueue)
    {
	ERR_(msg)("invalid argument.\n");
	return 0;
    }

    msgQueue->magic = 0;
    
    if( hCursorQueue == hQueue ) hCursorQueue = 0;
    if( hActiveQueue == hQueue ) hActiveQueue = 0;

    /* flush sent messages */
    QUEUE_FlushMessages( msgQueue );

    HeapLock( GetProcessHeap() );  /* FIXME: a bit overkill */

    /* Release per queue data if present */
    if ( msgQueue->pQData )
    {
        PERQDATA_Release( msgQueue->pQData );
        msgQueue->pQData = 0;
    }
    
    /* remove the message queue from the global link list */
    pPrev = &hFirstQueue;
    while (*pPrev && (*pPrev != hQueue))
    {
        MESSAGEQUEUE *msgQ = (MESSAGEQUEUE*)GlobalLock16(*pPrev);

        /* sanity check */
        if ( !msgQ || (msgQ->magic != QUEUE_MAGIC) )
        {
            /* HQUEUE link list is corrupted, try to exit gracefully */
            ERR_(msg)("HQUEUE link list corrupted!\n");
            pPrev = 0;
            break;
        }
        pPrev = &msgQ->next;
    }
    if (pPrev && *pPrev) *pPrev = msgQueue->next;
    msgQueue->self = 0;

    HeapUnlock( GetProcessHeap() );

    /* free up resource used by MESSAGEQUEUE structure */
    msgQueue->lockCount--;
    QUEUE_Unlock( msgQueue );
    
    return 1;
}


/***********************************************************************
 *           QUEUE_CreateSysMsgQueue
 *
 * Create the system message queue, and set the double-click speed.
 * Must be called only once.
 */
BOOL QUEUE_CreateSysMsgQueue( int size )
{
    /* Note: We dont need perQ data for the system message queue */
    if (!(hmemSysMsgQueue = QUEUE_CreateMsgQueue( FALSE )))
        return FALSE;
    FarSetOwner16( hmemSysMsgQueue, 0 );
    sysMsgQueue = (MESSAGEQUEUE *) GlobalLock16( hmemSysMsgQueue );
    return TRUE;
}


/***********************************************************************
 *           QUEUE_GetSysQueue
 */
MESSAGEQUEUE *QUEUE_GetSysQueue(void)
{
    return sysMsgQueue;
}


/***********************************************************************
 *           QUEUE_SetWakeBit
 *
 * See "Windows Internals", p.449
 */
static BOOL QUEUE_TrySetWakeBit( MESSAGEQUEUE *queue, WORD bit, BOOL always )
{
    BOOL wake = FALSE;

    EnterCriticalSection( &queue->cSection );

    TRACE_(msg)("queue = %04x (wm=%04x), bit = %04x, always = %d\n", 
	                queue->self, queue->wakeMask, bit, always );

    if ((queue->wakeMask & bit) || always)
    {
        if (bit & QS_MOUSE) pMouseQueue = queue;
        if (bit & QS_KEY) pKbdQueue = queue;
        queue->changeBits |= bit;
        queue->wakeBits   |= bit;
    }
    if (queue->wakeMask & bit)
    {
        queue->wakeMask = 0;
        wake = TRUE;
    }

    LeaveCriticalSection( &queue->cSection );

    if ( wake )
    {
        /* Wake up thread waiting for message */
        SERVER_START_REQ( wake_queue )
        {
            req->handle = queue->server_queue;
            req->bits   = bit;
            SERVER_CALL();
        }
        SERVER_END_REQ;
    }

    return wake;
}
void QUEUE_SetWakeBit( MESSAGEQUEUE *queue, WORD bit )
{
    QUEUE_TrySetWakeBit( queue, bit, TRUE );
}


/***********************************************************************
 *           QUEUE_ClearWakeBit
 */
void QUEUE_ClearWakeBit( MESSAGEQUEUE *queue, WORD bit )
{
    EnterCriticalSection( &queue->cSection );
    queue->changeBits &= ~bit;
    queue->wakeBits   &= ~bit;
    LeaveCriticalSection( &queue->cSection );
}

/***********************************************************************
 *           QUEUE_TestWakeBit
 */
WORD QUEUE_TestWakeBit( MESSAGEQUEUE *queue, WORD bit )
{
    WORD ret;
    EnterCriticalSection( &queue->cSection );
    ret = queue->wakeBits & bit;
    LeaveCriticalSection( &queue->cSection );
    return ret;
}


/***********************************************************************
 *           QUEUE_WaitBits
 *
 * See "Windows Internals", p.447
 *
 * return values:
 *    0 if exit with timeout
 *    1 otherwise
 */
int QUEUE_WaitBits( WORD bits, DWORD timeout )
{
    MESSAGEQUEUE *queue;
    HQUEUE16 hQueue;

    TRACE_(msg)("q %04x waiting for %04x\n", GetFastQueue16(), bits);

    hQueue = GetFastQueue16();
    if (!(queue = QUEUE_Lock( hQueue ))) return 0;
    
    for (;;)
    {
        DWORD dwlc;

        EnterCriticalSection( &queue->cSection );

        if (queue->changeBits & bits)
        {
            /* One of the bits is set; we can return */
            queue->wakeMask = 0;

            LeaveCriticalSection( &queue->cSection );
            QUEUE_Unlock( queue );
            return 1;
        }
        if (queue->wakeBits & QS_SENDMESSAGE)
        {
            /* Process the sent message immediately */
	    queue->wakeMask = 0;

            LeaveCriticalSection( &queue->cSection );
            QUEUE_ReceiveMessage( queue );
	    continue;				/* nested sm crux */
        }

        queue->wakeMask = bits | QS_SENDMESSAGE;
	TRACE_(msg)("%04x) wakeMask is %04x, waiting\n", queue->self, queue->wakeMask);
        LeaveCriticalSection( &queue->cSection );

        ReleaseThunkLock( &dwlc );
        if (dwlc) TRACE_(msg)("had win16 lock\n");
        WaitForSingleObject( queue->server_queue, timeout );
        if (dwlc) RestoreThunkLock( dwlc );
    }
}


/***********************************************************************
 *           QUEUE_AddSMSG
 *
 * This routine is called when a SMSG need to be added to one of the three
 * SM list.  (SM_PROCESSING_LIST, SM_PENDING_LIST, SM_WAITING_LIST)
 */
BOOL QUEUE_AddSMSG( MESSAGEQUEUE *queue, int list, SMSG *smsg )
{
    TRACE_(sendmsg)("queue=%x, list=%d, smsg=%p msg=%s\n", queue->self, list,
          smsg, SPY_GetMsgName(smsg->msg));
    
    switch (list)
    {
        case SM_PROCESSING_LIST:
            /* don't need to be thread safe, only accessed by the
             thread associated with the sender queue */
            smsg->nextProcessing = queue->smProcessing;
            queue->smProcessing = smsg;
            break;
            
        case SM_WAITING_LIST:
            /* don't need to be thread safe, only accessed by the
             thread associated with the receiver queue */
            smsg->nextWaiting = queue->smWaiting;
            queue->smWaiting = smsg;
            break;
            
        case SM_PENDING_LIST:
        {
            /* make it thread safe, could be accessed by the sender and
             receiver thread */
            SMSG **prev;

            EnterCriticalSection( &queue->cSection );
            smsg->nextPending = NULL;
            prev = &queue->smPending;
            while ( *prev )
                prev = &(*prev)->nextPending;
            *prev = smsg;
            LeaveCriticalSection( &queue->cSection );

            QUEUE_SetWakeBit( queue, QS_SENDMESSAGE );
            break;
        }

        default:
            ERR_(sendmsg)("Invalid list: %d", list);
            break;
    }

    return TRUE;
}


/***********************************************************************
 *           QUEUE_RemoveSMSG
 *
 * This routine is called when a SMSG needs to be removed from one of the three
 * SM lists (SM_PROCESSING_LIST, SM_PENDING_LIST, SM_WAITING_LIST).
 * If smsg == 0, remove the first smsg from the specified list
 */
SMSG *QUEUE_RemoveSMSG( MESSAGEQUEUE *queue, int list, SMSG *smsg )
{

    switch (list)
    {
        case SM_PROCESSING_LIST:
            /* don't need to be thread safe, only accessed by the
             thread associated with the sender queue */

            /* if smsg is equal to null, it means the first in the list */
            if (!smsg)
                smsg = queue->smProcessing;

            TRACE_(sendmsg)("queue=%x, list=%d, smsg=%p msg=%s\n", queue->self, list,
                  smsg, SPY_GetMsgName(smsg->msg));
            /* In fact SM_PROCESSING_LIST is a stack, and smsg
             should be always at the top of the list */
            if ( (smsg != queue->smProcessing) || !queue->smProcessing )
            {
                ERR_(sendmsg)("smsg not at the top of Processing list, smsg=0x%p queue=0x%p\n", smsg, queue);
                return 0;
            }
            else
            {
                queue->smProcessing = smsg->nextProcessing;
                smsg->nextProcessing = 0;
            }
            return smsg;

        case SM_WAITING_LIST:
            /* don't need to be thread safe, only accessed by the
             thread associated with the receiver queue */

            /* if smsg is equal to null, it means the first in the list */
            if (!smsg)
                smsg = queue->smWaiting;
            
            TRACE_(sendmsg)("queue=%x, list=%d, smsg=%p msg=%s\n", queue->self, list,
                  smsg, SPY_GetMsgName(smsg->msg));
            /* In fact SM_WAITING_LIST is a stack, and smsg
             should be always at the top of the list */
            if ( (smsg != queue->smWaiting) || !queue->smWaiting )
            {
                ERR_(sendmsg)("smsg not at the top of Waiting list, smsg=0x%p queue=0x%p\n", smsg, queue);
                return 0;
            }
            else
            {
                queue->smWaiting = smsg->nextWaiting;
                smsg->nextWaiting = 0;
            }
            return smsg;

        case SM_PENDING_LIST:
            /* make it thread safe, could be accessed by the sender and
             receiver thread */
            EnterCriticalSection( &queue->cSection );
    
            if (!smsg)
                smsg = queue->smPending;
	    if ( (smsg != queue->smPending) || !queue->smPending )
            {
                ERR_(sendmsg)("should always remove the top one in Pending list, smsg=0x%p queue=0x%p\n", smsg, queue);
		LeaveCriticalSection( &queue->cSection );
                return 0;
            }
            
            TRACE_(sendmsg)("queue=%x, list=%d, smsg=%p msg=%s\n", queue->self, list,
                  smsg, SPY_GetMsgName(smsg->msg));

            queue->smPending = smsg->nextPending;
            smsg->nextPending = 0;

            /* if no more SMSG in Pending list, clear QS_SENDMESSAGE flag */
            if (!queue->smPending)
                QUEUE_ClearWakeBit( queue, QS_SENDMESSAGE );
            
            LeaveCriticalSection( &queue->cSection );
            return smsg;

        default:
            ERR_(sendmsg)("Invalid list: %d\n", list);
            break;
    }

    return 0;
}


/***********************************************************************
 *           QUEUE_ReceiveMessage
 * 
 * This routine is called to check whether a sent message is waiting 
 * for the queue.  If so, it is received and processed.
 */
BOOL QUEUE_ReceiveMessage( MESSAGEQUEUE *queue )
{
    LRESULT       result = 0;
    SMSG          *smsg;
    MESSAGEQUEUE  *senderQ;

    EnterCriticalSection( &queue->cSection );
    if ( !((queue->wakeBits & QS_SENDMESSAGE) && queue->smPending) )
    {
        LeaveCriticalSection( &queue->cSection );
        return FALSE;
    }
    LeaveCriticalSection( &queue->cSection );

    TRACE_(sendmsg)("queue %04x\n", queue->self );

    /* remove smsg on the top of the pending list and put it in the processing list */
    smsg = QUEUE_RemoveSMSG(queue, SM_PENDING_LIST, 0);
    QUEUE_AddSMSG(queue, SM_WAITING_LIST, smsg);

    TRACE_(sendmsg)("RM: %s [%04x] (%04x -> %04x)\n",
       	    SPY_GetMsgName(smsg->msg), smsg->msg, smsg->hSrcQueue, smsg->hDstQueue );

    if (IsWindow( smsg->hWnd ))
    {
        WND *wndPtr = WIN_FindWndPtr( smsg->hWnd );
        DWORD extraInfo = queue->GetMessageExtraInfoVal; /* save ExtraInfo */

        /* use sender queue extra info value while calling the window proc */
        senderQ = QUEUE_Lock( smsg->hSrcQueue );
        if (senderQ)
        {
            queue->GetMessageExtraInfoVal = senderQ->GetMessageExtraInfoVal;
            QUEUE_Unlock( senderQ );
        }

        /* call the right version of CallWindowProcXX */
        if (smsg->flags & SMSG_WIN32)
        {
            TRACE_(sendmsg)("\trcm: msg is Win32\n" );
            if (smsg->flags & SMSG_UNICODE)
                result = CallWindowProcW( wndPtr->winproc,
                                            smsg->hWnd, smsg->msg,
                                            smsg->wParam, smsg->lParam );
            else
                result = CallWindowProcA( wndPtr->winproc,
                                            smsg->hWnd, smsg->msg,
                                            smsg->wParam, smsg->lParam );
        }
        else  /* Win16 message */
            result = CallWindowProc16( (WNDPROC16)wndPtr->winproc,
                                       (HWND16) smsg->hWnd,
                                       (UINT16) smsg->msg,
                                       LOWORD (smsg->wParam),
                                       smsg->lParam );

        queue->GetMessageExtraInfoVal = extraInfo;  /* Restore extra info */
        WIN_ReleaseWndPtr(wndPtr);
	TRACE_(sendmsg)("result =  %08x\n", (unsigned)result );
    }
    else WARN_(sendmsg)("\trcm: bad hWnd\n");

    
    /* set SMSG_SENDING_REPLY flag to tell ReplyMessage16, it's not
       an early reply */
    smsg->flags |= SMSG_SENDING_REPLY;
    ReplyMessage( result );

    TRACE_(sendmsg)("done!\n" );
    return TRUE;
}



/***********************************************************************
 *           QUEUE_AddMsg
 *
 * Add a message to the queue. Return FALSE if queue is full.
 */
BOOL QUEUE_AddMsg( HQUEUE16 hQueue, int type, MSG *msg, DWORD extraInfo )
{
    MESSAGEQUEUE *msgQueue;
    QMSG         *qmsg;


    if (!(msgQueue = QUEUE_Lock( hQueue ))) return FALSE;

    /* allocate new message in global heap for now */
    if (!(qmsg = (QMSG *) HeapAlloc( GetProcessHeap(), 0, sizeof(QMSG) ) ))
    {
        QUEUE_Unlock( msgQueue );
        return 0;
    }

    EnterCriticalSection( &msgQueue->cSection );

      /* Store message */
    qmsg->type = type;
    qmsg->msg = *msg;
    qmsg->extraInfo = extraInfo;

    /* insert the message in the link list */
    qmsg->nextMsg = 0;
    qmsg->prevMsg = msgQueue->lastMsg;

    if (msgQueue->lastMsg)
        msgQueue->lastMsg->nextMsg = qmsg;

    /* update first and last anchor in message queue */
    msgQueue->lastMsg = qmsg;
    if (!msgQueue->firstMsg)
        msgQueue->firstMsg = qmsg;
    
    msgQueue->msgCount++;

    LeaveCriticalSection( &msgQueue->cSection );

    QUEUE_SetWakeBit( msgQueue, QS_POSTMESSAGE );
    QUEUE_Unlock( msgQueue );
    
    return TRUE;
}



/***********************************************************************
 *           QUEUE_FindMsg
 *
 * Find a message matching the given parameters. Return -1 if none available.
 */
QMSG* QUEUE_FindMsg( MESSAGEQUEUE * msgQueue, HWND hwnd, int first, int last )
{
    QMSG* qmsg;

    EnterCriticalSection( &msgQueue->cSection );

    if (!msgQueue->msgCount)
        qmsg = 0;
    else if (!hwnd && !first && !last)
        qmsg = msgQueue->firstMsg;
    else
    {
        /* look in linked list for message matching first and last criteria */
        for (qmsg = msgQueue->firstMsg; qmsg; qmsg = qmsg->nextMsg)
        {
            MSG *msg = &(qmsg->msg);

            if (!hwnd || (msg->hwnd == hwnd))
            {
                if (!first && !last)
                    break;   /* found it */
                
                if ((msg->message >= first) && (!last || (msg->message <= last)))
                    break;   /* found it */
            }
	}
    }
    
    LeaveCriticalSection( &msgQueue->cSection );

    return qmsg;
}



/***********************************************************************
 *           QUEUE_RemoveMsg
 *
 * Remove a message from the queue (pos must be a valid position).
 */
void QUEUE_RemoveMsg( MESSAGEQUEUE * msgQueue, QMSG *qmsg )
{
    EnterCriticalSection( &msgQueue->cSection );

    /* set the linked list */
    if (qmsg->prevMsg)
        qmsg->prevMsg->nextMsg = qmsg->nextMsg;

    if (qmsg->nextMsg)
        qmsg->nextMsg->prevMsg = qmsg->prevMsg;

    if (msgQueue->firstMsg == qmsg)
        msgQueue->firstMsg = qmsg->nextMsg;

    if (msgQueue->lastMsg == qmsg)
        msgQueue->lastMsg = qmsg->prevMsg;

    /* deallocate the memory for the message */
    HeapFree( GetProcessHeap(), 0, qmsg );
    
    msgQueue->msgCount--;
    if (!msgQueue->msgCount) msgQueue->wakeBits &= ~QS_POSTMESSAGE;

    LeaveCriticalSection( &msgQueue->cSection );
}


/***********************************************************************
 *           QUEUE_WakeSomeone
 *
 * Wake a queue upon reception of a hardware event.
 */
static void QUEUE_WakeSomeone( UINT message )
{
    WND*	  wndPtr = NULL;
    WORD          wakeBit;
    HWND hwnd;
    HQUEUE16     hQueue = 0;
    MESSAGEQUEUE *queue = NULL;

    if (hCursorQueue)
        hQueue = hCursorQueue;

    if( (message >= WM_KEYFIRST) && (message <= WM_KEYLAST) )
    {
       wakeBit = QS_KEY;
       if( hActiveQueue )
           hQueue = hActiveQueue;
    }
    else 
    {
       wakeBit = (message == WM_MOUSEMOVE) ? QS_MOUSEMOVE : QS_MOUSEBUTTON;
       if( (hwnd = GetCapture()) )
	 if( (wndPtr = WIN_FindWndPtr( hwnd )) ) 
           {
               hQueue = wndPtr->hmemTaskQ;
               WIN_ReleaseWndPtr(wndPtr);
           }
    }

    if( (hwnd = GetSysModalWindow16()) )
    {
      if( (wndPtr = WIN_FindWndPtr( hwnd )) )
        {
            hQueue = wndPtr->hmemTaskQ;
            WIN_ReleaseWndPtr(wndPtr);
        }
    }

    if (hQueue)
    {
        queue = QUEUE_Lock( hQueue );
        QUEUE_SetWakeBit( queue, wakeBit );
        QUEUE_Unlock( queue );
        return;
    }

    /* Search for someone to wake */
    hQueue = hFirstQueue;
    while ( (queue = QUEUE_Lock( hQueue )) )
    {
        if (QUEUE_TrySetWakeBit( queue, wakeBit, FALSE )) 
        {
            QUEUE_Unlock( queue );
            return;
        }
        
        hQueue = queue->next; 
        QUEUE_Unlock( queue );
    }

    WARN_(msg)("couldn't find queue\n"); 
}


/***********************************************************************
 *           hardware_event
 *
 * Add an event to the system message queue.
 * Note: the position is relative to the desktop window.
 */
void hardware_event( UINT message, WPARAM wParam, LPARAM lParam,
		     int xPos, int yPos, DWORD time, DWORD extraInfo )
{
    MSG *msg;
    QMSG  *qmsg;
    int  mergeMsg = 0;

    if (!sysMsgQueue) return;

    EnterCriticalSection( &sysMsgQueue->cSection );

    /* Merge with previous event if possible */
    qmsg = sysMsgQueue->lastMsg;

    if ((message == WM_MOUSEMOVE) && sysMsgQueue->lastMsg)
    {
        msg = &(sysMsgQueue->lastMsg->msg);
        
	if ((msg->message == message) && (msg->wParam == wParam))
        {
            /* Merge events */
            qmsg = sysMsgQueue->lastMsg;
            mergeMsg = 1;
        }
    }

    if (!mergeMsg)
    {
        /* Should I limit the number of messages in
          the system message queue??? */

        /* Don't merge allocate a new msg in the global heap */
        
        if (!(qmsg = (QMSG *) HeapAlloc( GetProcessHeap(), 0, sizeof(QMSG) ) ))
        {
            LeaveCriticalSection( &sysMsgQueue->cSection );
            return;
        }
        
        /* put message at the end of the linked list */
        qmsg->nextMsg = 0;
        qmsg->prevMsg = sysMsgQueue->lastMsg;

        if (sysMsgQueue->lastMsg)
            sysMsgQueue->lastMsg->nextMsg = qmsg;

        /* set last and first anchor index in system message queue */
        sysMsgQueue->lastMsg = qmsg;
        if (!sysMsgQueue->firstMsg)
            sysMsgQueue->firstMsg = qmsg;
        
        sysMsgQueue->msgCount++;
    }

      /* Store message */
    msg = &(qmsg->msg);
    msg->hwnd    = 0;
    msg->message = message;
    msg->wParam  = wParam;
    msg->lParam  = lParam;
    msg->time    = time;
    msg->pt.x    = xPos;
    msg->pt.y    = yPos;
    qmsg->extraInfo = extraInfo;
    qmsg->type      = QMSG_HARDWARE;

    LeaveCriticalSection( &sysMsgQueue->cSection );

    QUEUE_WakeSomeone( message );
}

		    
/***********************************************************************
 *	     QUEUE_GetQueueTask
 */
HTASK16 QUEUE_GetQueueTask( HQUEUE16 hQueue )
{
    HTASK16 hTask = 0;
    
    MESSAGEQUEUE *queue = QUEUE_Lock( hQueue );

    if (queue)
    {
        hTask = queue->teb->htask16;
        QUEUE_Unlock( queue );
    }

    return hTask;
}



/***********************************************************************
 *           QUEUE_IncPaintCount
 */
void QUEUE_IncPaintCount( HQUEUE16 hQueue )
{
    MESSAGEQUEUE *queue;

    if (!(queue = QUEUE_Lock( hQueue ))) return;
    EnterCriticalSection( &queue->cSection );
    queue->wPaintCount++;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_SetWakeBit( queue, QS_PAINT );
    QUEUE_Unlock( queue );
}


/***********************************************************************
 *           QUEUE_DecPaintCount
 */
void QUEUE_DecPaintCount( HQUEUE16 hQueue )
{
    MESSAGEQUEUE *queue;

    if (!(queue = QUEUE_Lock( hQueue ))) return;
    EnterCriticalSection( &queue->cSection );
    queue->wPaintCount--;
    if (!queue->wPaintCount) queue->wakeBits &= ~QS_PAINT;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_Unlock( queue );
}


/***********************************************************************
 *           QUEUE_IncTimerCount
 */
void QUEUE_IncTimerCount( HQUEUE16 hQueue )
{
    MESSAGEQUEUE *queue;

    if (!(queue = QUEUE_Lock( hQueue ))) return;
    EnterCriticalSection( &queue->cSection );
    queue->wTimerCount++;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_SetWakeBit( queue, QS_TIMER );
    QUEUE_Unlock( queue );
}


/***********************************************************************
 *           QUEUE_DecTimerCount
 */
void QUEUE_DecTimerCount( HQUEUE16 hQueue )
{
    MESSAGEQUEUE *queue;

    if (!(queue = QUEUE_Lock( hQueue ))) return;
    EnterCriticalSection( &queue->cSection );
    queue->wTimerCount--;
    if (!queue->wTimerCount) queue->wakeBits &= ~QS_TIMER;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_Unlock( queue );
}


/***********************************************************************
 *		PostQuitMessage (USER.6)
 */
void WINAPI PostQuitMessage16( INT16 exitCode )
{
    PostQuitMessage( exitCode );
}


/***********************************************************************
 *		PostQuitMessage (USER32.@)
 *
 * PostQuitMessage() posts a message to the system requesting an
 * application to terminate execution. As a result of this function,
 * the WM_QUIT message is posted to the application, and
 * PostQuitMessage() returns immediately.  The exitCode parameter
 * specifies an application-defined exit code, which appears in the
 * _wParam_ parameter of the WM_QUIT message posted to the application.  
 *
 * CONFORMANCE
 *
 *  ECMA-234, Win32
 */
void WINAPI PostQuitMessage( INT exitCode )
{
    MESSAGEQUEUE *queue;

    if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return;
    EnterCriticalSection( &queue->cSection );
    queue->wPostQMsg = TRUE;
    queue->wExitCode = (WORD)exitCode;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_Unlock( queue );
}


/***********************************************************************
 *		GetWindowTask (USER.224)
 */
HTASK16 WINAPI GetWindowTask16( HWND16 hwnd )
{
    HTASK16 retvalue;
    WND *wndPtr = WIN_FindWndPtr( hwnd );

    if (!wndPtr) return 0;
    retvalue = QUEUE_GetQueueTask( wndPtr->hmemTaskQ );
    WIN_ReleaseWndPtr(wndPtr);
    return retvalue;
}

/***********************************************************************
 *		GetWindowThreadProcessId (USER32.@)
 */
DWORD WINAPI GetWindowThreadProcessId( HWND hwnd, LPDWORD process )
{
    DWORD retvalue;
    MESSAGEQUEUE *queue;

    WND *wndPtr = WIN_FindWndPtr( hwnd );
    if (!wndPtr) return 0;

    queue = QUEUE_Lock( wndPtr->hmemTaskQ );
    WIN_ReleaseWndPtr(wndPtr);

    if (!queue) return 0;

    if ( process ) *process = (DWORD)queue->teb->pid;
    retvalue = (DWORD)queue->teb->tid;

    QUEUE_Unlock( queue );
    return retvalue;
}

/***********************************************************************
 *		SetMessageQueue (USER.266)
 */
BOOL16 WINAPI SetMessageQueue16( INT16 size )
{
    return SetMessageQueue( size );
}


/***********************************************************************
 *		SetMessageQueue (USER32.@)
 */
BOOL WINAPI SetMessageQueue( INT size )
{
    /* now obsolete the message queue will be expanded dynamically
     as necessary */

    /* access the queue to create it if it's not existing */
    GetFastQueue16();

    return TRUE;
}

/***********************************************************************
 *		InitThreadInput (USER.409)
 */
HQUEUE16 WINAPI InitThreadInput16( WORD unknown, WORD flags )
{
    HQUEUE16 hQueue;
    MESSAGEQUEUE *queuePtr;

    TEB *teb = NtCurrentTeb();

    if (!teb)
        return 0;

    hQueue = teb->queue;
    
    if ( !hQueue )
    {
        /* Create thread message queue */
        if( !(hQueue = QUEUE_CreateMsgQueue( TRUE )))
        {
            ERR_(msg)("failed!\n");
            return FALSE;
	}
        
        /* Link new queue into list */
        queuePtr = QUEUE_Lock( hQueue );
        queuePtr->teb = NtCurrentTeb();

        HeapLock( GetProcessHeap() );  /* FIXME: a bit overkill */
        SetThreadQueue16( 0, hQueue );
        teb->queue = hQueue;
            
        queuePtr->next  = hFirstQueue;
        hFirstQueue = hQueue;
        HeapUnlock( GetProcessHeap() );
        
        QUEUE_Unlock( queuePtr );
    }

    return hQueue;
}

/***********************************************************************
 *		GetQueueStatus (USER.334)
 */
DWORD WINAPI GetQueueStatus16( UINT16 flags )
{
    MESSAGEQUEUE *queue;
    DWORD ret;

    if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return 0;
    EnterCriticalSection( &queue->cSection );
    ret = MAKELONG( queue->changeBits, queue->wakeBits );
    queue->changeBits = 0;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_Unlock( queue );
    
    return ret & MAKELONG( flags, flags );
}

/***********************************************************************
 *		GetQueueStatus (USER32.@)
 */
DWORD WINAPI GetQueueStatus( UINT flags )
{
    MESSAGEQUEUE *queue;
    DWORD ret;

    if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return 0;
    EnterCriticalSection( &queue->cSection );
    ret = MAKELONG( queue->changeBits, queue->wakeBits );
    queue->changeBits = 0;
    LeaveCriticalSection( &queue->cSection );
    QUEUE_Unlock( queue );
    
    return ret & MAKELONG( flags, flags );
}


/***********************************************************************
 *		GetInputState (USER.335)
 */
BOOL16 WINAPI GetInputState16(void)
{
    return GetInputState();
}

/***********************************************************************
 *		WaitForInputIdle (USER32.@)
 */
DWORD WINAPI WaitForInputIdle (HANDLE hProcess, DWORD dwTimeOut)
{
    DWORD cur_time, ret;
    HANDLE idle_event = -1;

    SERVER_START_REQ( wait_input_idle )
    {
        req->handle = hProcess;
        req->timeout = dwTimeOut;
        if (!(ret = SERVER_CALL_ERR())) idle_event = req->event;
    }
    SERVER_END_REQ;
    if (ret) return 0xffffffff;  /* error */
    if (!idle_event) return 0;  /* no event to wait on */

    cur_time = GetTickCount();

    TRACE_(msg)("waiting for %x\n", idle_event );
    while ( dwTimeOut > GetTickCount() - cur_time || dwTimeOut == INFINITE ) 
    {
        ret = MsgWaitForMultipleObjects ( 1, &idle_event, FALSE, dwTimeOut, QS_SENDMESSAGE );
        if ( ret == ( WAIT_OBJECT_0 + 1 )) 
        {
            MESSAGEQUEUE * queue;
            if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return 0xFFFFFFFF;
            QUEUE_ReceiveMessage ( queue );
            QUEUE_Unlock ( queue );
            continue; 
        }
        if ( ret == WAIT_TIMEOUT || ret == 0xFFFFFFFF ) 
        {
            TRACE_(msg)("timeout or error\n");
            return ret;
        }
        else 
        {
            TRACE_(msg)("finished\n");
            return 0;
        }
    }

    return WAIT_TIMEOUT;
}

/***********************************************************************
 *		GetInputState (USER32.@)
 */
BOOL WINAPI GetInputState(void)
{
    MESSAGEQUEUE *queue;
    BOOL ret;

    if (!(queue = QUEUE_Lock( GetFastQueue16() )))
        return FALSE;
    EnterCriticalSection( &queue->cSection );
    ret = queue->wakeBits & (QS_KEY | QS_MOUSEBUTTON);
    LeaveCriticalSection( &queue->cSection );
    QUEUE_Unlock( queue );

    return ret;
}

/***********************************************************************
 *		UserYield (USER.332)
 *		UserYield16 (USER32.@)
 */
void WINAPI UserYield16(void)
{
    MESSAGEQUEUE *queue;

    /* Handle sent messages */
    queue = QUEUE_Lock( GetFastQueue16() );

    while ( queue && QUEUE_ReceiveMessage( queue ) )
        ;

    QUEUE_Unlock( queue );

    /* Yield */
    OldYield16();

    /* Handle sent messages again */
    queue = QUEUE_Lock( GetFastQueue16() );

    while ( queue && QUEUE_ReceiveMessage( queue ) )
        ;

    QUEUE_Unlock( queue );
}

/***********************************************************************
 *		GetMessagePos (USER.119) (USER32.@)
 * 
 * The GetMessagePos() function returns a long value representing a
 * cursor position, in screen coordinates, when the last message
 * retrieved by the GetMessage() function occurs. The x-coordinate is
 * in the low-order word of the return value, the y-coordinate is in
 * the high-order word. The application can use the MAKEPOINT()
 * macro to obtain a POINT structure from the return value. 
 *
 * For the current cursor position, use GetCursorPos().
 *
 * RETURNS
 *
 * Cursor position of last message on success, zero on failure.
 *
 * CONFORMANCE
 *
 * ECMA-234, Win32
 *
 */
DWORD WINAPI GetMessagePos(void)
{
    MESSAGEQUEUE *queue;
    DWORD ret;

    if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return 0;
    ret = queue->GetMessagePosVal;
    QUEUE_Unlock( queue );

    return ret;
}


/***********************************************************************
 *		GetMessageTime (USER.120) (USER32.@)
 *
 * GetMessageTime() returns the message time for the last message
 * retrieved by the function. The time is measured in milliseconds with
 * the same offset as GetTickCount().
 *
 * Since the tick count wraps, this is only useful for moderately short
 * relative time comparisons.
 *
 * RETURNS
 *
 * Time of last message on success, zero on failure.
 *
 * CONFORMANCE
 *
 * ECMA-234, Win32
 *  
 */
LONG WINAPI GetMessageTime(void)
{
    MESSAGEQUEUE *queue;
    LONG ret;

    if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return 0;
    ret = queue->GetMessageTimeVal;
    QUEUE_Unlock( queue );
    
    return ret;
}


/***********************************************************************
 *		GetMessageExtraInfo (USER.288) (USER32.@)
 */
LONG WINAPI GetMessageExtraInfo(void)
{
    MESSAGEQUEUE *queue;
    LONG ret;

    if (!(queue = QUEUE_Lock( GetFastQueue16() ))) return 0;
    ret = queue->GetMessageExtraInfoVal;
    QUEUE_Unlock( queue );

    return ret;
}


/**********************************************************************
 *		AttachThreadInput (USER32.@) Attaches input of 1 thread to other
 *
 * Attaches the input processing mechanism of one thread to that of
 * another thread.
 *
 * RETURNS
 *    Success: TRUE
 *    Failure: FALSE
 *
 * TODO:
 *    1. Reset the Key State (currenly per thread key state is not maintained)
 */
BOOL WINAPI AttachThreadInput( 
    DWORD idAttach,   /* [in] Thread to attach */
    DWORD idAttachTo, /* [in] Thread to attach to */
    BOOL fAttach)   /* [in] Attach or detach */
{
    MESSAGEQUEUE *pSrcMsgQ = 0, *pTgtMsgQ = 0;
    BOOL16 bRet = 0;

    SetLastError(ERROR_CALL_NOT_IMPLEMENTED);

    /* A thread cannot attach to itself */
    if ( idAttach == idAttachTo )
        goto CLEANUP;

    /* According to the docs this method should fail if a
     * "Journal record" hook is installed. (attaches all input queues together)
     */
    if ( HOOK_IsHooked( WH_JOURNALRECORD ) )
        goto CLEANUP;
        
    /* Retrieve message queues corresponding to the thread id's */
    pTgtMsgQ = QUEUE_Lock( GetThreadQueue16( idAttach ) );
    pSrcMsgQ = QUEUE_Lock( GetThreadQueue16( idAttachTo ) );

    /* Ensure we have message queues and that Src and Tgt threads
     * are not system threads.
     */
    if ( !pSrcMsgQ || !pTgtMsgQ || !pSrcMsgQ->pQData || !pTgtMsgQ->pQData )
        goto CLEANUP;

    if (fAttach)   /* Attach threads */
    {
        /* Only attach if currently detached  */
        if ( pTgtMsgQ->pQData != pSrcMsgQ->pQData )
        {
            /* First release the target threads perQData */
            PERQDATA_Release( pTgtMsgQ->pQData );
        
            /* Share a reference to the source threads perQDATA */
            PERQDATA_Addref( pSrcMsgQ->pQData );
            pTgtMsgQ->pQData = pSrcMsgQ->pQData;
        }
    }
    else    /* Detach threads */
    {
        /* Only detach if currently attached */
        if ( pTgtMsgQ->pQData == pSrcMsgQ->pQData )
        {
            /* First release the target threads perQData */
            PERQDATA_Release( pTgtMsgQ->pQData );
        
            /* Give the target thread its own private perQDATA once more */
            pTgtMsgQ->pQData = PERQDATA_CreateInstance();
        }
    }

    /* TODO: Reset the Key State */

    bRet = 1;      /* Success */
    
CLEANUP:

    /* Unlock the queues before returning */
    if ( pSrcMsgQ )
        QUEUE_Unlock( pSrcMsgQ );
    if ( pTgtMsgQ )
        QUEUE_Unlock( pTgtMsgQ );
    
    return bRet;
}