/*+
 *  File name:
 *      clxtshar.c
 *  Contents:
 *      Client extension loaded by RDP client
 *
 *      Copyright (C) 1998-1999 Microsoft Corp.
 --*/

#include    <windows.h>
#include    <windowsx.h>
#include    <winsock.h>
#include    <string.h>
#include    <malloc.h>
#include    <stdlib.h>
#include    <stdio.h>
#include    <stdarg.h>
#ifndef OS_WINCE
    #include    <direct.h>
#endif  // OS_WINCE

#ifndef OS_WINCE
#ifdef  OS_WIN32
    #include    <process.h>
#endif  // OS_WIN32
#endif  // !OS_WINCE

#include    "clxexport.h"
#include    "clxtshar.h"

#define WM_CLIPBOARD    (WM_USER)   // Internal notifcation to send
                                    // our clipboard

#ifdef  OS_WIN32
#ifndef OS_WINCE
/*++
 *  Function:
 *      DllMain
 *  Description:
 *      Dll entry point for win32 (no WinCE)
 --*/
int APIENTRY DllMain(HINSTANCE hDllInst,
                    DWORD   dwReason,
                    LPVOID  fImpLoad)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        g_hInstance = hDllInst;
        TRACE((INFO_MESSAGE, TEXT("Clx attached\n")));

        // Check the key "Allow Background Input"
        // If not set pop a message for that
        if (!_CheckRegistrySettings())
            MessageBox(NULL, "CLXTSHAR.DLL: Can't find registry key:\n"
            "HKEY_CURRENT_USER\\Software\\Microsoft\\Terminal Server Client\\"
            "Allow Background Input.\n"
            "In order to work properly "
            "CLX needs this key to be set to 1", "Warning", 
            MB_OK);

        _GetIniSettings();
    }

    if (dwReason == DLL_PROCESS_DETACH)
    {
        TRACE((INFO_MESSAGE, TEXT("Clx detached\n")));
    }

    return TRUE;    
}
#endif  // !OS_WINCE
#endif  // OS_WIN32

#ifdef  OS_WINCE
/*++
 *  Function:
 *      dllentry
 *  Description:
 *      Dll entry point for wince
 --*/
BOOL __stdcall dllentry(HINSTANCE hDllInst,
                    DWORD   dwReason,
                    LPVOID  fImpLoad)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        g_hInstance = hDllInst;
        TRACE((INFO_MESSAGE, TEXT("Clx attached\n")));
        if (!_StartAsyncThread())
            TRACE((ERROR_MESSAGE,
                   TEXT("Can't start AsyncThread. TCP unusable\n")));

        _GetIniSettings();
    }

    if (dwReason == DLL_PROCESS_DETACH)
    {
        TRACE((INFO_MESSAGE, TEXT("Clx detached\n")));
        _CloseAsyncThread();
    }

    return TRUE;
}
#endif  // OS_WIN32

#ifdef  OS_WIN16
/*++
 *  Function:
 *      LibMain
 *  Description:
 *      Dll entry point for win16
 --*/
int CALLBACK LibMain(HINSTANCE hInstance,
                     WORD dataSeg,
                     WORD heapSize,
                     LPSTR pCmdLine)
{

    // Check if we are already initialized
    // Only one client is allowed in Win16 environment
    // so, only one dll can be loaded at a time
    if (g_hInstance)
        goto exitpt;

    g_hInstance = hInstance;

    // Check the key "Allow Background Input"
    // If not set pop a message for that
    if (!_CheckIniSettings())
        MessageBox(NULL, "CLXTSHAR.DLL: Can't find key: "
        "Allow Background Input in mstsc.ini, section \"\"\n"
        "In order to work properly "
        "CLX needs this key to be set to 1", "Warning",
        MB_OK);

        _GetIniSettings();

exitpt:

    return TRUE;
}
#endif  // OS_WIN16

/*++
 *  Function:
 *      ClxInitialize
 *  Description:
 *      Initilizes a context for the current session
 *      reads the command line paramters and determines
 *      the mode wich will run the extension: local or RCLX (Remote CLient
 *      eXecution)
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClInfo     - RDP client info
 *      ppClx       - context info
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      !mstsc after the dll is loaded
 --*/
BOOL 
CLXAPI
ClxInitialize(PCLINFO pClInfo, PCLXINFO *ppClx)
{
    BOOL rv = FALSE;
    HWND hwndSMC;

#ifdef  OS_WIN32
#ifndef OS_WINCE

    // We have enough problems in stress with early unloaded
    // dll, reference it now and keep it up until the process
    // dies
    LoadLibrary("clxtshar.dll");

#endif  // !OS_WINCE
#endif  // OS_WIN32

    hwndSMC = _ParseCmdLine(pClInfo->pszCmdLine);

    if (IS_RCLX && !WS_Init())
    {
        TRACE((ERROR_MESSAGE, TEXT("Can't init winsock\n")));
        goto exitpt;
    }

    if (g_pClx) 
    // Should not be called twice
    {
        TRACE((WARNING_MESSAGE, TEXT("g_pClx is not null. Reentered ?!\n")));
        goto exitpt;
    }

    *ppClx = (PCLXINFO)_CLXALLOC(sizeof(**ppClx));

    g_pClx = (*ppClx);

    if (!*ppClx)
        goto exitpt;

    // Clear the structure
    memset(*ppClx, 0, sizeof(**ppClx));

    // put init of g_pClx here
    g_pClx->bClipboardReenter = (ULONG)-1;
    //


    // Remember client's input window
    (*ppClx)->hwndMain = pClInfo->hwndMain;

    if (pClInfo->hwndMain)
#ifdef  OS_WINCE
        g_hRDPInst = GetCurrentProcessId();
#else   // !OS_WINCE
#ifdef  _WIN64
        g_hRDPInst = (HINSTANCE)GetWindowLongPtr((*ppClx)->hwndMain, GWLP_HINSTANCE);
#else   // !_WIN64
#ifdef  OS_WIN32
	    g_hRDPInst = (HINSTANCE)GetWindowLong((*ppClx)->hwndMain, GWL_HINSTANCE);
#endif  // OS_WIN32
#endif  // _WIN64
#ifdef  OS_WIN16
	    g_hRDPInst = (HINSTANCE)GetWindowWord((*ppClx)->hwndMain, GWW_HINSTANCE);
#endif  // OS_WIN16
#endif  // !OS_WINCE

#ifndef OS_WINCE
#ifdef  OS_WIN32
    // and dwProcessId
    (*ppClx)->dwProcessId = GetCurrentProcessId();
#endif  // OS_WIN32
#endif  // !OS_WINCE

    if (IS_RCLX)
    {
        (*ppClx)->hSocket = INVALID_SOCKET;
        RClx_CreateWindow(g_hInstance);
    } 
#ifdef  OS_WIN32
#ifndef OS_WINCE
    else {
        if (!((*ppClx)->hwndSMC = hwndSMC))
            (*ppClx)->hwndSMC = _FindSMCWindow(*ppClx);
    }
#endif  // !OS_WINCE
#endif  // OS_WIN32

   if (g_hWindow) // REMOVED: && g_nMyReconId)
        PostMessage(g_hWindow, WM_TIMER, 0, 0);

    rv = TRUE;
exitpt:
    return rv;
}

/*++
 *  Function:
 *      ClxEvent
 *  Description:
 *      Notifies tclient.dll that some event happend.
 *      Connect/disconnect.
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      Event       - can be one of the following:
 *                    CLX_EVENT_CONNECT
 *                    CLX_EVENT_DISCONNECT
 *                    CLX_EVENT_LOGON
 *  Called by:
 *      !mstsc  on event
 *      alse some of the internal functions call this, especialy
 *      to notify that the client can't connect:
 *      ClxTerminate
 *      _GarbageCollecting when an error box is popped
 --*/
VOID
CLXAPI
ClxEvent(PCLXINFO pClx, CLXEVENT Event, ULONG ulResult)
{
    UINT uiMessage = 0;
#ifdef  OS_WIN16
    ULONG   lResult = ulResult;
#else   // !OS_WIN16
    LONG_PTR lResult = ulResult;
#endif

    if (!pClx)
        goto exitpt;

#ifdef  VLADIMIS
#pragma message("Disable this peace before checkin")
    if (Event == CLX_EVENT_SHADOWBITMAPDC)
    {
        pClx->hdcShadowBitmap = (HDC)lResult;
        goto exitpt;
    } else if (Event == CLX_EVENT_SHADOWBITMAP)
    {
        pClx->hShadowBitmap = (HBITMAP)lResult;
        goto exitpt;
    }
#endif // VLADIMIS

    if (IS_RCLX)
    {
        RClx_SendEvent(pClx, Event, ulResult);
    } 
#ifndef OS_WINCE
    else {

        if (!_CheckWindow(pClx))
            goto exitpt;

        if (Event == CLX_EVENT_DISCONNECT)
            uiMessage = WM_FB_DISCONNECT;
        else if (Event == CLX_EVENT_CONNECT)
        {
            uiMessage = WM_FB_CONNECT;
            lResult   = (LONG_PTR)pClx->hwndMain;
        }
        else if (Event == CLX_EVENT_LOGON)
        // lResult contains the session ID
            uiMessage = WM_FB_LOGON;

        if (uiMessage)
        {
#ifdef  OS_WIN32
            _ClxSendMessage(pClx->hwndSMC, 
                        uiMessage, 
                        (WPARAM)lResult, 
                        pClx->dwProcessId);

#endif  // OS_WIN32
#ifdef	OS_WIN16
	    if (g_hRDPInst)
	        SendMessage(pClx->hwndSMC,
                        uiMessage,
                        g_hRDPInst,
                        (LPARAM)lResult);
#endif	// OS_WIN16
        }
    }
#endif  // !OS_WINCE

exitpt:
    ;
}

/*++
 *  Function:
 *      ClxTextOut
 *  Description:
 *      Notifies tclient.dll that TEXTOUT order is recieved.
 *      Passes the string to the dll. Supported only in Win32
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      pText       - buffer containing the string
 *      textLength  - string length
 *  Called by:
 *      !mstsc on receiving textout order
 --*/
VOID
CLXAPI
ClxTextOut(PCLXINFO pClx, PVOID pText, INT textLength)
{
    if (!pClx || !(*((UINT16 *)pText)))
        goto exitpt;

    if (IS_RCLX)
    {
        RClx_SendTextOut(pClx, pText, textLength);
        goto exitpt;
    }

#ifdef  OS_WIN32
#ifndef OS_WINCE
    if (!_CheckWindow(pClx))
        goto exitpt;

    if (!pClx->hMapF)
        if (!_OpenMapFile(pClx, 0))
            goto exitpt;

    if (_SaveInMapFile(pClx->hMapF, pText, textLength, pClx->dwProcessId))
        _ClxSendMessage(pClx->hwndSMC, 
                    WM_FB_TEXTOUT, 
                    (WPARAM)pClx->dwProcessId, 
                    (LPARAM)pClx->hMapF);
#endif  // !OS_WINCE
#endif  // OS_WIN32

exitpt:
    ;
}

/*++
 *  Function:
 *      ClxTerminate
 *  Description:
 *      Frees all alocations from ClxInitialize
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *  Called by:
 *      !mstsc before the dll is unloaded and client exit
 --*/
VOID
CLXAPI
ClxTerminate(PCLXINFO pClx)
{
    PCLXVCHANNEL pNext;

    if (!pClx)
        goto exitpt;

    ClxEvent(pClx, CLX_EVENT_DISCONNECT, 0);

    if (IS_RCLX)
    {
        pClx->alive = FALSE;
        RClx_Disconnect(pClx);
    } 
#ifdef  OS_WIN32
#ifndef OS_WINCE
    else {
        if(pClx->hMapF)
	    CloseHandle(pClx->hMapF);
        _ClxDestroySendMsgThread(g_pClx);
    }
#endif  // !OS_WINCE
#endif  // OS_WIN32

    if (pClx->uiReconnectTimer)
    {
        KillTimer(g_hWindow, pClx->uiReconnectTimer);
        pClx->uiReconnectTimer = 0;
    }

    if (pClx->pRequest)
        _CLXFREE(pClx->pRequest);

    _CLXFREE(pClx);
    g_pClx = NULL;

    // dispose g_pVChannels;
    while(g_pVChannels)
    {
        pNext = g_pVChannels->pNext;
        free(g_pVChannels);
        g_pVChannels = pNext;
    }

    RClx_DestroyWindow();

    if (IS_RCLX)
        WSACleanup();

exitpt:
    ;
}

/*
 * Void functions exported to the RDP client
 */
VOID
CLXAPI
ClxConnect(PCLXINFO pClx, LPTSTR lpsz)
{
}

VOID
CLXAPI
ClxDisconnect(PCLXINFO pClx)
{
}


/*++
 *  Function:
 *      ClxDialog
 *  Description:
 *      The RDP client is ready with the connect dialog.
 *      In RCLX mode this means that we can connect to the test controler
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - connection context
 *      hwnd    - handle to the dialog window
 *  Called by:
 *      !mstsc when the connect dialog is ready
 --*/
VOID
CLXAPI
ClxDialog(PCLXINFO pClx, HWND hwnd)
{
    if (!pClx)
        goto exitpt;

    pClx->hwndDialog = hwnd;

    if (hwnd == NULL)
    // Dialog disappears
        goto exitpt;

    if (g_hWindow)
        PostMessage(g_hWindow, WM_TIMER, 0, 0);
    else
        TRACE((ERROR_MESSAGE, TEXT("No g_hWindow in ClxDialog\n")));

exitpt:
    ;
}

/*++
 *  Function:
 *      ClxBitmap
 *  Description:
 *      Send a received bitmap to tclient.dll
 *      Works on Win16/Win32/WinCE in RCLX mode
 *      and on Win32 for local mode
 *  Arguments:
 *      pClx        - context
 *      cxSize, cySize - size of the bitmap
 *      pBuffer     - bitmap bits
 *      nBmiSize    - size of BITMAPINFO
 *      pBmi        - BITMAPINFO
 *  Called by:
 *      UHDrawMemBltOrder!mstsc
 *      ClxGlyphOut
 --*/
VOID
CLXAPI
ClxBitmap(
        PCLXINFO pClx,
        UINT cxSize,
        UINT cySize,
        PVOID pBuffer,
        UINT  nBmiSize,
        PVOID pBmi)
{
#ifndef OS_WINCE
#ifdef  OS_WIN32
    UINT   nSize, nBmpSize;
    PBMPFEEDBACK pView;
#endif  // OS_WIN32
#endif  // !OS_WINCE

    if (!g_GlyphEnable)
        goto exitpt;

    if (!pClx)
        goto exitpt;

    if (nBmiSize && !pBmi)
        goto exitpt;

    if (IS_RCLX)
    {
        RClx_SendBitmap(pClx, cxSize, cySize, pBuffer, nBmiSize, pBmi);
        goto exitpt;
    }
#ifdef  OS_WIN32
#ifndef OS_WINCE
    if (!_CheckWindow(pClx))
        goto exitpt;

    if (!nBmiSize)
        nBmpSize = (cxSize * cySize ) >> 3;
    else
    {
        nBmpSize = ((PBITMAPINFO)pBmi)->bmiHeader.biSizeImage;
        if (!nBmpSize)
            nBmpSize = (cxSize * cySize * 
                        ((PBITMAPINFO)pBmi)->bmiHeader.biBitCount) >> 3;
    }

    nSize = nBmpSize + nBmiSize + sizeof(*pView);
    if (!nSize)
        goto exitpt;

    if (!pClx->hMapF)
        if (!_OpenMapFile(pClx, nSize))
            goto exitpt;

    if (nSize > pClx->nMapSize)
        if (!_ReOpenMapFile(pClx, nSize))
            goto exitpt;

    pView = MapViewOfFile(pClx->hMapF,
                          FILE_MAP_ALL_ACCESS,
                          0,
                          0,
                          nSize);

    if (!pView)
        goto exitpt;

    pView->lProcessId = pClx->dwProcessId;
    pView->bmpsize = nBmpSize;
    pView->bmiSize = nBmiSize;
    pView->xSize = cxSize;
    pView->ySize = cySize;

    if (pBmi)
        CopyMemory(&(pView->BitmapInfo), pBmi, nBmiSize);

    CopyMemory((BYTE *)(&(pView->BitmapInfo)) + nBmiSize, pBuffer, nBmpSize);

    if (!nBmiSize)
    {
        // This is glyph, strip it to the skin
        _StripGlyph((BYTE *)(&pView->BitmapInfo), &cxSize, cySize);
        nBmpSize = (cxSize * cySize ) >> 3;
        pView->bmpsize = nBmpSize;
        pView->xSize = cxSize;
    }

    UnmapViewOfFile(pView);

    _ClxSendMessage(pClx->hwndSMC, 
                WM_FB_BITMAP, 
                (WPARAM)pClx->dwProcessId, 
                (LPARAM)pClx->hMapF);

#endif  // !OS_WINCE
#endif  // OS_WIN32

exitpt:
    ;
}

/*++
 *  Function:
 *      ClxGlyphOut
 *  Description:
 *      Send a glyph to tclient.dll
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      cxBits,cyBits - glyph size
 *      pBuffer     - the glyph
 *  Called by:
 *      GHOutputBuffer!mstsc
 --*/
VOID
CLXAPI
ClxGlyphOut(
        PCLXINFO pClx,
        UINT cxBits,
        UINT cyBits,
        PVOID pBuffer)
{
    if (g_GlyphEnable)
        ClxBitmap(pClx, cxBits, cyBits, pBuffer, 0, NULL);
}

/*++
 *  Function:
 *      ClxGlyphOut
 *  Description:
 *      Send a glyph to tclient.dll
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      cxBits,cyBits - glyph size
 *      pBuffer     - the glyph
 *  Called by:
 *      GHOutputBuffer!mstsc
 --*/
BOOL
CLXAPI
ClxGetClientData(
    PCLX_CLIENT_DATA pClntData
    )
{
    BOOL rv = FALSE;

    if (!pClntData)
    {
        TRACE((ERROR_MESSAGE, TEXT("ClxGetClientData: parameter is NULL\n")));
        goto exitpt;
    }

    memset(pClntData, 0, sizeof(*pClntData));

    if (!g_pClx)
    {
        TRACE((ERROR_MESSAGE, TEXT("ClxGetClientData: Clx has no context\n")));
        goto exitpt;
    }

    pClntData->hScreenDC        = g_pClx->hdcShadowBitmap;
    pClntData->hScreenBitmap    = g_pClx->hShadowBitmap;
    pClntData->hwndMain         = g_pClx->hwndMain;
    pClntData->hwndDialog       = g_pClx->hwndDialog;
    pClntData->hwndInput        = g_pClx->hwndInput;

    rv = TRUE;
exitpt:
    return rv;    
}

/*++
 *  Function:
 *      _ParseCmdLine
 *  Description:
 *      Retreives WHND of tclient.dll feedback window
 *      passed by the command line
 *      or retreives TestServer name if clxtshar will be
 *      executed in RCLX mode
 *      Win32/Win16/WinCE
 *  Arguments:
 *      szCmdLine   - command line
 *  Return value:
 *      The window handle
 *  Called by:
 *      ClxInitialize
 --*/
HWND _ParseCmdLine(LPCTSTR szCmdLine)
{
    HWND        hwnd = NULL;
    LPCTSTR     pszwnd, pszdot, pszend;
    INT         nCounter;

    if (!szCmdLine)
        goto exitpt;

    TRACE((INFO_MESSAGE, TEXT("Command line: %s\n"), szCmdLine));

    // Check for _RECONIDOPT(ReconID) option
    pszwnd = _CLX_strstr(szCmdLine, TEXT(_RECONIDOPT));
    if (!pszwnd)
        goto skip_reconidopt;

    pszwnd += _CLX_strlen(TEXT(_RECONIDOPT));
    g_nMyReconId = _CLX_atol(pszwnd);
    
skip_reconidopt:
    // Check for _HWNDOPT(hSMC) option
    pszwnd = _CLX_strstr(szCmdLine, TEXT(_HWNDOPT));

    if (!pszwnd)
        goto exitpt;

    // Goto the parameter
    pszwnd += _CLX_strlen(TEXT(_HWNDOPT));

    // Find the end of the paramter
    pszend = _CLX_strchr(pszwnd, TEXT(' '));
    if (!pszend)
        pszend = pszwnd + _CLX_strlen(pszwnd);

    // Check if paramter is valid host name, i.e. not a number
    pszdot = _CLX_strchr(pszwnd, TEXT('.'));

    if (isalpha(*pszwnd) || (pszdot && (pszdot < pszend)))
    {
    // This is RCLX mode, grab the TestServer name
#ifdef  OS_WIN16
        INT len;
#else   // !OS_WIN16
        LONG_PTR len;
#endif  // OS_WIN16

        len = pszend - pszwnd;
        if (!len || len >= sizeof(g_szTestServer))
        {
            TRACE((WARNING_MESSAGE,
                   TEXT("TestServer name is too long\n")));
            goto exitpt;
        }

        for (nCounter = 0; 
             nCounter < len;
             g_szTestServer[nCounter] = (CHAR)(pszwnd[nCounter]), nCounter++)
            ;

        g_szTestServer[len] = 0;

#ifdef  UNICODE
        TRACE((INFO_MESSAGE,
               L"RCLX mode, connecting to test controler: %S\n",
               g_szTestServer));
#else   // !UNICODE
        TRACE((INFO_MESSAGE,
               "RCLX mode, connecting to test controler: %s\n",
               g_szTestServer));
#endif  // !UNICODE

    } else {
    // local execution, hwnd passed

#ifdef  _WIN64
        hwnd = (HWND) _atoi64(pszwnd);
#else   // !_WIN64
        hwnd = (HWND) _CLX_atol(pszwnd);
#endif  // !_WIN64

        TRACE((INFO_MESSAGE,
           TEXT("Local mode. Sending messages to smclient. HWND=0x%x\n"), 
           hwnd));
    }

exitpt:
    return hwnd;
}

#ifndef OS_WINCE
/*++
 *  Function:
 *      _EnumWindowsProcForSMC
 *  Description:
 *      Searches for the feedback window by class name
 *      When found, sends a WM_FB_ACCEPTME to ensure that
 *      this is the right window handle
 *      Win32/Win16/!WinCE
 *  Arguments:
 *      hWnd    - current window
 *      lParam  - unused
 *  Return value:
 *      FALSE if found
 *  Called by:
 *      _FindSMCWindow thru EnumWindows
 --*/
BOOL CALLBACK LOADDS _EnumWindowsProcForSMC( HWND hWnd, LPARAM lParam )
{
    TCHAR    classname[128];

    BOOL    bCont = TRUE;

    if (GetClassName(hWnd, classname, sizeof(classname)))
    {
        if (!
            _CLX_strcmp(classname, TEXT(_TSTNAMEOFCLAS)) &&
#ifdef  OS_WIN32
             SendMessage(hWnd, WM_FB_ACCEPTME, 0, GetCurrentProcessId()))
#endif
#ifdef  OS_WIN16
             SendMessage(hWnd, WM_FB_ACCEPTME, (WPARAM)g_hRDPInst, 0))
#endif
        {
            *((HWND*)lParam) = hWnd;
            bCont = FALSE;
        }
    }
    return bCont;
}

/*++
 *  Function:
 *      _FindSMCWindow
 *  Description:
 *      Finds the tclient feedback window
 *      Win32/Win16/!WinCE
 *  Arguments:
 *      pClx    - context
 *  Return value:
 *      The window handle
 *  Called by:
 *      ClxInitialize, _CheckWindow
 --*/
HWND _FindSMCWindow(PCLXINFO pClx)
{
    HWND hwndFound = NULL;

    EnumWindows(_EnumWindowsProcForSMC, (LPARAM)&hwndFound);

    return hwndFound;
}

/*++
 *  Function:
 *      _CheckWindow
 *  Description:
 *      Checks the feedback window and if neccessary finds it
 *      Win32/Win16/!WinCE
 *  Arguments:
 *      pClx    - context
 *  Return value:
 *      Feedback window handle
 *  Called by:
 *      ClxEvetm ClxTextOut, ClxBitmap
 --*/
HWND _CheckWindow(PCLXINFO pClx)
{
    if (!pClx->hwndSMC)
    {
        pClx->hwndSMC = _FindSMCWindow(pClx);

        if (pClx->hwndSMC)
        {
            TRACE((INFO_MESSAGE, 
            TEXT("SMC window found:0x%x\n"), 
            pClx->hwndSMC));
        }
    } else {
#ifdef  _WIN64
        if (!GetWindowLongPtr(pClx->hwndSMC, GWLP_HINSTANCE))
#else   // !_WIN64
#ifdef  OS_WIN32
        if (!GetWindowLong(pClx->hwndSMC, GWL_HINSTANCE))
#endif
#ifdef  OS_WIN16
        if (!GetWindowWord(pClx->hwndSMC, GWW_HINSTANCE))
#endif
#endif  // _WIN64
        {
            TRACE((WARNING_MESSAGE, TEXT("SMC window lost\n")));
            pClx->hwndSMC = NULL;
        }
    }

    return (pClx->hwndSMC);
}
#endif  // !OS_WINCE

#ifdef  OS_WIN32
#ifndef OS_WINCE
/*++
 *  Function:
 *      _OpenMapFile
 *  Description:
 *      Opens a shared memeory for passing feedback to tclient.dll
 *      Win32/!Win16/!WinCE
 *  Return value:
 *      TRUE if handle is allocated successfully
 *  Called by:
 *      ClxTextOut, ClxBitmap
 --*/
BOOL _OpenMapFile(PCLXINFO pClx, UINT nSize)
{
    HANDLE hMapF;
    UINT nPageAligned;

    if (!nSize)
        nPageAligned = ((sizeof(FEEDBACKINFO) / CLX_ONE_PAGE) + 1) * 
                                                            CLX_ONE_PAGE;
    else
        nPageAligned = ((nSize / CLX_ONE_PAGE) + 1) * CLX_ONE_PAGE;

    hMapF = CreateFileMapping(INVALID_HANDLE_VALUE,   //PG.SYS
                              NULL,                 // no security
                              PAGE_READWRITE,
                              0,                    // Size high
                              nPageAligned,         // Size low (1 page)
                              NULL);           

    pClx->nMapSize = (hMapF)?nPageAligned:0;
        
    pClx->hMapF = hMapF;
    return (hMapF != NULL);
}

/*++
 *  Function:
 *      _ReOpenMapFile
 *  Description:
 *      Closes and opens a new shared memory with larger size
 *      Win32/!Win16/!WinCE
 *  Arguments:
 *      pClx    - context
 *      newSize - size of the new memory
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      ClxBitmap
 --*/
BOOL _ReOpenMapFile(PCLXINFO pClx, UINT newSize)
{
    HANDLE hNewMapF;
    UINT    nPageAligned;

    nPageAligned = ((newSize / CLX_ONE_PAGE) + 1) * CLX_ONE_PAGE;
    if (pClx->hMapF)
        CloseHandle(pClx->hMapF);
    hNewMapF = CreateFileMapping(INVALID_HANDLE_VALUE,   //PG.SYS
                              NULL,                 // no security
                              PAGE_READWRITE,
                              0,                    // Size high
                              nPageAligned,         // Size low
                              NULL);

    pClx->nMapSize = (hNewMapF)?nPageAligned:0;
    pClx->hMapF = hNewMapF;

    return (hNewMapF != NULL);
}

/*++
 *  Function:
 *      _SaveinMapFile
 *  Description:
 *      Saves a string into the shared memory
 *      Win32/!Win16/!WinCE
 *  Arguments:
 *      hMapF       - handle to the map file
 *      str         - the string
 *      strsize     - size of the string
 *      dwProcessId - our process Id
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      ClxTextOut
 --*/
BOOL _SaveInMapFile(HANDLE hMapF, LPVOID *str, int strsize, DWORD dwProcessId)
{
    BOOL rv = FALSE, count = 0;
    PFEEDBACKINFO pView;
    DWORD laste;

    pView = MapViewOfFile(hMapF,
                          FILE_MAP_ALL_ACCESS,
                          0,
                          0,
                          sizeof(*pView));

    if (!pView)
        goto exitpt;

    pView->dwProcessId = dwProcessId;

    strsize = (strsize > sizeof(pView->string)/sizeof(WCHAR))?
              sizeof(pView->string)/sizeof(WCHAR):
              strsize;
    CopyMemory(pView->string, str, strsize*sizeof(WCHAR)); 
    ((WCHAR *)(pView->string))[strsize] = 0;
    pView->strsize = strsize;

    UnmapViewOfFile(pView);

    rv = TRUE;

exitpt:

    return rv;
}

/*++
 *  Function:
 *      _CheckRegistrySettings
 *  Description:
 *      Checks if the registry settings are OK for running clxtshar
 *      "Allow Background Input" must be set to 1 for proper work
 *      Win32/!Win16/!WinCE
 *  Return value:
 *      TRUE if the settings are OK
 *  Called by:
 *      DllMain
 --*/
BOOL _CheckRegistrySettings(VOID)
{
    HKEY    key;
    DWORD   disposition;
    DWORD   keyType;
    DWORD   value;
    DWORD   cbData;
    BOOL    rv = FALSE;
    LONG    sysrc;

    sysrc = RegCreateKeyEx(HKEY_CURRENT_USER,
                           REG_BASE,
                           0,                   /* reserved             */
                           NULL,                /* class                */
                           REG_OPTION_NON_VOLATILE,
                           KEY_ALL_ACCESS,
                           NULL,                /* security attributes  */
                           &key,
                           &disposition);

    cbData = sizeof(value);
    sysrc = RegQueryValueEx(key,
                ALLOW_BACKGROUND_INPUT,
                0,              // reserved
                &keyType,       // returned type
                (LPBYTE)&value, // data pointer
                &cbData);

    if (sysrc != ERROR_SUCCESS)
    {
        TRACE((WARNING_MESSAGE, 
            TEXT("RegQueryValueEx failed, status = %d\n"), sysrc));
        goto exitpt;
    }

    if (keyType != REG_DWORD || cbData != sizeof(value))
    {
        TRACE((WARNING_MESSAGE, 
            TEXT("Mismatch in type/size of registry entry\n")));
        goto exitpt;
    }

    rv = (value == 1);

exitpt:
    return rv;
}

#endif  // !OS_WINCE
#endif  // OS_WIN32

#ifdef  OS_WIN16
/*++
 *  Function:
 *      _CheckRegistrySettings
 *  Description:
 *      Checks if the ini settings are OK for running clxtshar
 *      "Allow Background Input" must be set to 1 for proper work
 *      !Win32/Win16/!WinCE
 *  Return value:
 *      TRUE if the settings are OK
 *  Called by:
 *      DllMain
 --*/
BOOL    _CheckIniSettings(VOID)
{
    UINT nABI;

    nABI = GetPrivateProfileInt("", 
                                ALLOW_BACKGROUND_INPUT, 
                                0, 
                                "mstsc.ini");

    return (nABI == 1);
}
#endif  // OS_WIN16

/*++
 *  Function:
 *      _GetIniSettings
 *  Description:
 *      Gets the verbose level for printing debug messages
 *      ini file: smclient.ini
 *      section : clx
 *      key     : verbose, value: 0-4 (0-(default) no debug spew, 4 all debug)
 *      key     : GlyphEnable, value: 0(default), 1 - Enables/Disables glyph sending
 *      Win32/Win16/WinCE
 *  Called by:
 *      DllMain, dllentry, LibMain
 --*/
VOID _GetIniSettings(VOID)
{
#ifdef  OS_WINCE
    g_VerboseLevel = 4;
    g_GlyphEnable  = 1;
#else   // !OS_WINCE
    CHAR    szIniFileName[_MAX_PATH];
    const   CHAR  smclient_ini[] = "\\smclient.ini";
    const   CHAR  clx_ini_section[] = "clx";

    *szIniFileName = 0;
    if (!_getcwd (
        szIniFileName,
        sizeof(szIniFileName) - strlen(smclient_ini) - 1)
    )
    {
        TRACE((ERROR_MESSAGE, TEXT("Current directory length too long.\n")));
    }
    strcat(szIniFileName, smclient_ini);

    // Get the timeout value
    g_VerboseLevel = GetPrivateProfileInt(
            clx_ini_section,
            "verbose",
            g_VerboseLevel,
            szIniFileName);

    g_GlyphEnable = GetPrivateProfileInt(
            clx_ini_section,
            "GlyphEnable",
            g_GlyphEnable,
            szIniFileName);
#endif  // !OS_WINCE

    GetPrivateProfileString(
        TEXT("tclient"),
        TEXT("UIYesNoDisconnect"),
        TEXT(YES_NO_SHUTDOWN),
        g_strYesNoShutdown,
        sizeof(g_strYesNoShutdown),
        szIniFileName
    );

    GetPrivateProfileString(
        TEXT("tclient"),
        TEXT("UIDisconnectDialogBox"),
        TEXT(DISCONNECT_DIALOG_BOX),
        g_strDisconnectDialogBox,
        sizeof(g_strDisconnectDialogBox),
        szIniFileName
    );

    GetPrivateProfileString(
        TEXT("tclient"),
        TEXT("UIClientCaption"),
        TEXT(CLIENT_CAPTION),
        g_strClientCaption,
        sizeof(g_strClientCaption),
        szIniFileName
    );
}

/*++
 *  Function:
 *      _StripGlyph
 *  Description:
 *      Strips leading and trailing blank ... BITS
 *      Yes, bits. The glyph must be aligned from left and right on bit
 *      And glyph width must be aligned on word
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pData   - the glyph bits
 *      pxSize  - glyph width
 *      ySize   - glyph height
 *  Called by:
 *      ClxBitmap
 --*/
VOID _StripGlyph(LPBYTE pData, UINT *pxSize, UINT ySize)
{
    UINT xSize = *pxSize;
    UINT leftBytes, leftBits;
    UINT riteBytes, riteBits;
    UINT xBytes = xSize >> 3;
    UINT xScan, yScan, xFinal;
    BOOL bScan, bAddByte;
    BYTE mask;
    BYTE *pSrc, *pDst;

    if (!pData || !xBytes || !ySize)
        goto exitpt;

    leftBytes = riteBytes = 0;
    leftBits  = riteBits  = 0;
    *pxSize = 0;        // Insurance for bad exit

    // Scan from left for first nonzero byte
    bScan = TRUE;
    while(bScan)
    {
        for (yScan = 0; yScan < ySize && bScan; yScan ++)
            bScan = (pData[yScan*xBytes + leftBytes] == 0);

        if (bScan)
        {
            leftBytes++;
            bScan = (leftBytes < xBytes);
        }
    }

    // Trash if blank
    if (leftBytes == xBytes)
        goto exitpt;

    // Scan from left for most left nonzero bit
    for(yScan = 0; yScan < ySize; yScan ++)
    {
        UINT bitc = 0;
        BYTE b = pData[yScan*xBytes + leftBytes];

        while (b)
        {
            b >>= 1;
            bitc ++;
        }
        if (bitc > leftBits)
            leftBits = bitc;
    }

    if (!leftBits)
    // There's something wrong
        goto exitpt;

    leftBits = 8 - leftBits;

    // So far so good. Check the ri(gh)te side
    bScan = TRUE;
    while(bScan)
    {
        for(yScan = 0 ; yScan < ySize && bScan; yScan ++)
            bScan = (pData[(yScan + 1)*xBytes - 1 - riteBytes] == 0);

        if (bScan)
        {
            riteBytes ++;
            bScan = (riteBytes < xBytes);
        }
    }

    // Scan from rite for most rite nonzero bit
    for(yScan = 0; yScan < ySize; yScan ++) 
    {
        UINT bitc = 0;
        BYTE b = pData[(yScan+1)*xBytes - 1 - riteBytes];

        while(b)
        {
            b <<= 1;
            bitc ++;
        }
        if (bitc > riteBits)
            riteBits = bitc;
    }
    riteBits = 8 - riteBits;

    // Cool, now get the final width
    xFinal = xSize - riteBits - leftBits - ((leftBytes + riteBytes) << 3);
    // align it and get bytes
    xFinal = (xFinal + 8) >> 3;

    // Now smoothly move the bitmap to the new location
    pDst = pData;
    mask = BitMask[leftBits];
    bAddByte = xFinal & 1;

    for (yScan = 0; yScan < ySize; yScan ++)
    {

        pSrc = pData + yScan*xBytes + leftBytes;
        for(xScan = 0; xScan < xFinal; xScan ++, pDst++, pSrc++)
        {
            BYTE b = *pSrc;
            BYTE r;

            r = (pSrc[1] & mask) >> (8 - leftBits);

            b <<= leftBits;
            b |= r;
            (*pDst) = b;
        }
        pDst[-1] &= BitMask[8 - (riteBits + leftBits) % 8];

        if (bAddByte)
        {
            (*pDst) = 0;
            pDst++;
        }
    }

    // BUG: Yes, this is a real bug. But removing it means to
    // rerecord all glyph database and the impact for
    // glyph recognition is not so bad
    //if (bAddByte)
    //    xFinal++;

    *pxSize = xFinal << 3;
exitpt:
    ;
}


/*++
 *  Function:
 *      LocalPrintMessage
 *  Description:
 *      Prints debugging and warning/error messages
 *      Win32/Win16/WinCE
 *  Arguments:
 *      errlevel    - level of the message to print
 *      format      - print format
 *  Called by:
 *      every TRACE line
 --*/
VOID __cdecl LocalPrintMessage(INT errlevel, LPCTSTR format, ...)
{
    TCHAR szBuffer[256];
    TCHAR *type;
    va_list     arglist;
    int nchr;

    if (errlevel >= g_VerboseLevel)
        goto exitpt;

    va_start (arglist, format);
    nchr = _CLX_vsnprintf (szBuffer, sizeof(szBuffer), format, arglist);
    va_end (arglist);

    switch(errlevel)
    {
    case INFO_MESSAGE:      type = TEXT("CLX INF:"); break;
    case ALIVE_MESSAGE:     type = TEXT("CLX ALV:"); break;
    case WARNING_MESSAGE:   type = TEXT("CLX WRN:"); break;
    case ERROR_MESSAGE:     type = TEXT("CLX ERR:"); break;
    default: type = TEXT("UNKNOWN:");
    }

    OutputDebugString(type);
    OutputDebugString(szBuffer);
exitpt:
    ;
}


/*++
 *  Function:
 *      _ClxAssert
 *  Description:
 *      Asserts boolean expression
 *      Win32/Win16/WinCE
 *  Arguments:
 *      bCond       - boolean condition
 *      filename    - source file of the assertion
 *      line        - line of the assertion
 *  Called by:
 *      every ASSERT line
 --*/
VOID    _ClxAssert( LPCTSTR filename, INT line)
{
    TRACE((ERROR_MESSAGE, 
            TEXT("ASSERT: %s line %d\n"), filename, line));

    DebugBreak();
}
/*
 *  RCLX (Remote CLient eXecution) functions
 */

/*++
 *  Function:
 *      RClx_SendClientInfo
 *  Description:
 *      Sends platform specific information to the test controller
 *      Allows reconnection to a previous thread on the test ctrler
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      _ClxWndProc on WM_TIMER message
 --*/
BOOL
RClx_SendClientInfo(PCLXINFO pClx)
{
    LPCSTR szClientInfo;
    RCLXCLIENTINFOFEED ClntInfo;
    RCLXFEEDPROLOG  FeedProlog;
    BOOL rv = FALSE;

    ASSERT(pClx->hSocket != INVALID_SOCKET);

    TRACE((ALIVE_MESSAGE,
           TEXT("Sending Client info\n")));

#ifdef  OS_WIN16
    szClientInfo = "WIN16";
#endif  // OS_WIN16
#ifdef  OS_WIN32
#ifndef OS_WINCE
    szClientInfo = "WIN32";
#else
    szClientInfo = "WINCE";
#endif  // OS_WINCE
#endif  // OS_WIN32
    strcpy(ClntInfo.szClientInfo, szClientInfo);

    if (!g_nMyReconId)
    {
        ClntInfo.nReconnectAct = 0;
        ClntInfo.ReconnectID   = 0;
    } else {
        ClntInfo.nReconnectAct = 1;
        ClntInfo.ReconnectID   = g_nMyReconId;
    }

    FeedProlog.FeedType = FEED_CLIENTINFO;
    FeedProlog.HeadSize = sizeof(ClntInfo);
    FeedProlog.TailSize = 0;

    rv = RClx_SendBuffer(pClx->hSocket,
                         &FeedProlog,
                         sizeof(FeedProlog));

    rv = RClx_SendBuffer(pClx->hSocket,
                         &ClntInfo,
                         sizeof(ClntInfo));

    if (rv)
        pClx->bClientInfoSent = TRUE;

    return rv;
}

/*++
 *  Function:
 *      RClx_Connect
 *  Description:
 *      Connects to the TestServer. The connect call is blocking
 *      After the connection succeeds "selects" the socket for
 *      async read, write is blocking
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      _ClxWndProc on WM_TIMER message
 --*/
BOOL RClx_Connect(PCLXINFO pClx)
{
    SOCKET  hRemote;
    BOOL    rv = FALSE;
    INT     optval;

    ASSERT(pClx);

    if (pClx->hSocket != INVALID_SOCKET)
    {
        TRACE((WARNING_MESSAGE, 
            TEXT("RClx_Connect called more than once\n")));
        rv = TRUE;
        goto exitpt;
    }

#ifdef  UNICODE
    TRACE((INFO_MESSAGE, 
           L"Connecting to: %S:%d\n", g_szTestServer, g_nPort));
#else   // !UNICODE
    TRACE((INFO_MESSAGE,
           "Connecting to: %s:%d\n", g_szTestServer, g_nPort));
#endif  // !UNICODE

    hRemote = socket(AF_INET, SOCK_STREAM, 0);
    if (hRemote == INVALID_SOCKET)
    {
        TRACE((ERROR_MESSAGE, 
            TEXT("Can't create socket: %d\n"), WSAGetLastError()));
        goto exitpt;
    }

    optval = 1;
    setsockopt(hRemote, 
               IPPROTO_TCP, 
               TCP_NODELAY, 
               (const char *)&optval, 
               sizeof(optval));
    setsockopt(hRemote, 
               SOL_SOCKET,  
               SO_DONTLINGER, 
               (const char *)&optval, 
               sizeof(optval));

    if ((!pClx->sinTestSrv.sin_addr.s_addr ||
        pClx->sinTestSrv.sin_addr.s_addr == INADDR_NONE) &&
        (pClx->sinTestSrv.sin_addr.s_addr = inet_addr(g_szTestServer)) 
                                                == INADDR_NONE)
    {
        struct hostent *phostent;

        if ((phostent = gethostbyname(g_szTestServer)) == NULL)
        {
#ifdef  UNICODE
            TRACE((ERROR_MESSAGE,
                   L"gethostbyname for %S failed: %d\n",
                   g_szTestServer, WSAGetLastError()));
#else   // !UNICODE
            TRACE((ERROR_MESSAGE,
                   "gethostbyname for %s failed: %d\n",
                   g_szTestServer, WSAGetLastError()));
#endif  // !UNICODE
            goto cleanup;
        }
        pClx->sinTestSrv.sin_addr.s_addr = *(u_long*)(phostent->h_addr);
    }

    pClx->sinTestSrv.sin_family = PF_INET;
    pClx->sinTestSrv.sin_port = htons(g_nPort);

    if (connect(hRemote, 
                (SOCKADDR *)&(pClx->sinTestSrv), 
                sizeof(pClx->sinTestSrv)) 
                == SOCKET_ERROR)
    {
#ifdef  UNICODE
        TRACE((WARNING_MESSAGE,
               L"Can't connect to %S: %d\n",
               g_szTestServer, WSAGetLastError()));
#else   // !UNICODE
        TRACE((WARNING_MESSAGE,
               "Can't connect to %s: %d\n",
               g_szTestServer, WSAGetLastError()));
#endif  // !UNICODE
        goto cleanup;
    }

    pClx->bClientInfoSent = FALSE;

    pClx->hSocket = hRemote;
    if (WSAAsyncSelect(hRemote, g_hWindow, WM_WSOCK, FD_CLOSE|FD_READ) ==
        SOCKET_ERROR)
    {
        TRACE((ERROR_MESSAGE,
               TEXT("WSAAsyncSelect failed: %d\n"),
               WSAGetLastError()));
        goto cleanup;
    }

    // If the socket is disconnected somewhere between connect and select
    // we'll miss the notification, so try to read
    // and have chance to fail
    PostMessage(g_hWindow, WM_WSOCK, hRemote, FD_READ);

    rv = TRUE;
exitpt:
    return rv;
cleanup:
    pClx->hSocket = hRemote;
    RClx_Disconnect(pClx);
    return rv;
}


/*++
 *  Function:
 *      RClx_Disconnect
 *  Description:
 *      Gracefully closes the socket to the TestServer. Deinitializes
 *      some vars in the context. Removes garbage windows
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *  Called by:
 *      ClxTerminate
 *      RClx_Connect on error
 *      RClx_SendEvent on EVENT_DIACONNECT
 *      _ClxWndProc on WM_WSOCK and error
 --*/
VOID RClx_Disconnect(PCLXINFO pClx)
{
    INT     recvresult;
    CHAR    tBuf[128];

    if (pClx && pClx->hSocket != INVALID_SOCKET)
    {
        WSAAsyncSelect(pClx->hSocket, g_hWindow, 0, 0);

        shutdown(pClx->hSocket, SD_SEND);
        do {
            recvresult = recv(pClx->hSocket, tBuf, sizeof(tBuf), 0);
        } while (recvresult && recvresult != SOCKET_ERROR);

        closesocket(pClx->hSocket);
        pClx->hSocket = INVALID_SOCKET;
    }

    // Zero some vars
    pClx->hwndInput = NULL;
    pClx->RClxInfo.szHydraServer[0] = 0;

    if (pClx->alive)
    {
        // attempt to close the client
        _AttemptToCloseTheClient();
        g_pClx->bCloseTrys = TRUE;
    }
    else if (g_hWindow && !g_nMyReconId)
    // Retry to connect
    {
        TRACE((INFO_MESSAGE, 
            TEXT("Disconnected from test server.Trying to reconnect\n")));

        // If there's no timer looping
        // Start connecting by posting a message
        if (!pClx->uiReconnectTimer)
            pClx->uiReconnectTimer = SetTimer(g_hWindow, RCLX_RECONNECT_TIMERID,
                                              RCLX_RECONNECTELAPSETIME, NULL);

        if (!pClx->uiReconnectTimer)
            PostMessage(g_hWindow, WM_TIMER, 0, 0);
    }
}

/*++
 *  Function:
 *      RClx_SendBuffer
 *  Description:
 *      Sends a buffer thru socket. The socket must be BLOCKING
 *      so, all the buffer is sent after this function exits
 *      Win32/Win16/WinCE
 *  Arguments:
 *      hSocket     - the socket
 *      pBuffer     - the buffer
 *      nSize       - buffer size
 *  Return value:
 *      TRUE on success, FALSE if the connection failed
 *  Called by:
 *      RClx_SendEvent
 *      RClx_SendBitmap
 *        
 --*/
BOOL 
RClx_SendBuffer(SOCKET hSocket, PVOID pBuffer, DWORD nSize)
{
    INT     result = 0;
    DWORD   nBytesToSend = nSize;
    UINT   nBytesToSend2;
    BYTE    HUGEMOD *pBuffPtr = pBuffer;

    ASSERT(hSocket != INVALID_SOCKET);
    ASSERT(pBuffer);

    if (!nSize)
        goto exitpt;

    do {
#ifdef  OS_WIN16
        nBytesToSend2 = (UINT)((nBytesToSend > 0x1000)?0x1000:nBytesToSend);
#else
        nBytesToSend2 = nBytesToSend;
#endif  // OS_WIN16

        result = send(hSocket, pBuffPtr, nBytesToSend2, 0);

        if (result != SOCKET_ERROR)
        {
            nBytesToSend -= result;
            pBuffPtr += result;
        } else
        if (WSAGetLastError() == WSAEWOULDBLOCK)
        {
        // The socket is blocked, wait on select until it's writable
            FD_SET fd;

            FD_ZERO(&fd);
            FD_SET(hSocket, &fd);

            select(-1, NULL, &fd, NULL, NULL);
        }
    } while ((result != SOCKET_ERROR || WSAGetLastError() == WSAEWOULDBLOCK) && 
             nBytesToSend);

exitpt:
    return (result != SOCKET_ERROR);
}


/*++
 *  Function:
 *      RClx_SendEvent
 *  Description:
 *      Sends an event to the TestServer
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *      Event   - the event
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      ClxEvent
 --*/
BOOL RClx_SendEvent(PCLXINFO pClx, CLXEVENT Event, DWORD dwParam)
{
    BOOL rv = FALSE;

    if (Event == CLX_EVENT_DISCONNECT)
    {
        pClx->alive = FALSE;
        RClx_Disconnect(pClx);
    }

    if (Event == CLX_EVENT_CONNECT)
    {
        RCLXFEEDPROLOG FeedProlog;

        pClx->alive = TRUE;
        FeedProlog.FeedType = FEED_CONNECT;
        FeedProlog.HeadSize = 0;
        FeedProlog.TailSize = 0;
        rv = RClx_SendBuffer(pClx->hSocket, 
                             &FeedProlog, 
                             sizeof(FeedProlog));
    }

    if (Event == CLX_EVENT_LOGON)
    {
        RCLXFEEDPROLOG FeedProlog;
        UINT32  uSessionID  = dwParam;

        pClx->alive = TRUE;
        FeedProlog.FeedType = FEED_LOGON;
        FeedProlog.HeadSize = sizeof(uSessionID);
        FeedProlog.TailSize = 0;
        rv = RClx_SendBuffer(pClx->hSocket,
                             &FeedProlog,
                             sizeof(FeedProlog));

        rv = RClx_SendBuffer(pClx->hSocket,
                             &uSessionID,
                             sizeof(uSessionID));
    }

    return rv;
}

/*++
 *  Function:
 *      RClx_SendClipbaord
 *  Description:
 *      Sends the current clipbaord content to the test controller
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      uiFormat    - desired clipboard format
 *      nSize       - size of the data
 *      pClipboard  - the clipboard data
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      _ClxWndProc on WM_CLIPBOARD message
 --*/
BOOL
RClx_SendClipboard(
    PCLXINFO    pClx, 
    UINT        uiFormat, 
    UINT32      nSize, 
    VOID        HUGEMOD *pClipboard)
{
    BOOL rv = FALSE;

    RCLXFEEDPROLOG FeedProlog;
    RCLXCLIPBOARDFEED RClxClipboard;

    ASSERT(pClx);
    // if nSize == 0, then pClipboard == 0
    ASSERT((nSize && pClipboard) || (!nSize && !pClipboard));

    RClxClipboard.uiFormat = uiFormat;
    RClxClipboard.nClipBoardSize = nSize;

    FeedProlog.FeedType = FEED_CLIPBOARD;
    FeedProlog.HeadSize = sizeof(RClxClipboard);
    FeedProlog.TailSize = nSize;
    rv = RClx_SendBuffer(pClx->hSocket,
                         &FeedProlog,
                         sizeof(FeedProlog));

    if (!rv)
        goto exitpt;

    TRACE((INFO_MESSAGE,
           TEXT("Sending the clipboard, FormatID=%d, Size=%ld\n"),
           uiFormat, nSize));

    rv = RClx_SendBuffer(pClx->hSocket,
                         &RClxClipboard,
                         sizeof(RClxClipboard));

    if (!rv)
        goto exitpt;

    if (pClipboard)
        rv = RClx_SendBuffer(pClx->hSocket,
                             pClipboard,
                             nSize);

exitpt:
    return rv;
}

/*++
 *  Function:
 *      RClx_SendTextOut
 *  Description:
 *      Sends text out order to the smclient in RCLX mode
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      pText       - unicode string
 *      textLength  - the string length
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      ClxTextOut
 --*/
BOOL
RClx_SendTextOut(PCLXINFO pClx, PVOID pText, INT textLength)
{
    BOOL rv = FALSE;
    RCLXFEEDPROLOG FeedProlog;
    RCLXTEXTFEED   FeedText;
    UINT16  *szString; 

    ASSERT(pClx);
    if (!pText || !textLength)
        goto exitpt;

    FeedProlog.FeedType = FEED_TEXTOUT;
    FeedProlog.HeadSize = sizeof(FeedText);
    FeedProlog.TailSize = (textLength + 1) * sizeof(UINT16);

    szString = _alloca((textLength + 1) * sizeof(UINT16));
    if (!szString)
        goto exitpt;

    memcpy(szString, pText, textLength * sizeof(UINT16));
    szString[textLength] = 0;

    rv = RClx_SendBuffer(pClx->hSocket,
                         &FeedProlog,
                         sizeof(FeedProlog));

    if (!rv)
        goto exitpt;

    rv = RClx_SendBuffer(pClx->hSocket,
                         &FeedText,
                         sizeof(FeedText));

    if (!rv)
        goto exitpt;

    rv = RClx_SendBuffer(pClx->hSocket,
                         szString,
                         FeedProlog.TailSize);

exitpt:
    return rv;
}

/*++
 *  Function:
 *      RClx_SendBitmap
 *  Description:
 *      Sends a bitmap to the TestController
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *      cxSize  - bitmap width
 *      cySize  - bitmap height
 *      pBuffer - pointer to bitmap bits
 *      nBmiSize- BitmapInfo length
 *      pBmi    - pointer to BITMAPINFO
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      ClxBitmap
 --*/
BOOL RClx_SendBitmap(
        PCLXINFO pClx,
        UINT cxSize,
        UINT cySize,
        PVOID pBuffer,
        UINT  nBmiSize,
        PVOID pBmi)
{
    BOOL rv = FALSE;
    RCLXFEEDPROLOG FeedProlog;
    RCLXBITMAPFEED FeedBitmap;
    UINT   nExtraBmi;
    UINT   nBmpSize;
    BYTE *pLocalBits;

    ASSERT(pClx);
    ASSERT(pClx->hSocket != INVALID_SOCKET);

    if (nBmiSize > sizeof(FeedBitmap.BitmapInfo))
    {
        TRACE((WARNING_MESSAGE,
               TEXT("BitmapInfo size larger than expected. Ignoring\n")));
        goto exitpt;
    }

    // Get bitmap size
    if (!nBmiSize)
    {
        nBmpSize = (cxSize * cySize ) >> 3;
    } else {
        nBmpSize = (UINT)(((PBITMAPINFO)pBmi)->bmiHeader.biSizeImage);
        if (!nBmpSize)
            nBmpSize = (cxSize * cySize *
                        ((PBITMAPINFO)pBmi)->bmiHeader.biBitCount) >> 3;
    }

    pLocalBits = _alloca(nBmpSize);
    if (!pLocalBits)
        goto exitpt;

    memcpy(pLocalBits, pBuffer, nBmpSize);

    if (!nBmiSize)
    {
         // this is glyph, strip it !
        _StripGlyph(pLocalBits, &cxSize, cySize);
        nBmpSize = (cxSize * cySize ) >> 3;
    }

    // Prepare the prolog
    FeedProlog.FeedType = FEED_BITMAP;
    nExtraBmi = sizeof(FeedBitmap.BitmapInfo) - nBmiSize;
    FeedProlog.HeadSize = sizeof(FeedBitmap) - nExtraBmi;
    FeedProlog.TailSize = nBmpSize;

    FeedBitmap.bmpsize = nBmpSize;
    FeedBitmap.bmisize = nBmiSize;
    FeedBitmap.xSize   = cxSize;
    FeedBitmap.ySize   = cySize;
    memcpy(&FeedBitmap.BitmapInfo, 
           pBmi, 
           sizeof(FeedBitmap.BitmapInfo) - nExtraBmi);

    if (!RClx_SendBuffer(pClx->hSocket, 
                         &FeedProlog, 
                         sizeof(FeedProlog)))
    {
        TRACE((ERROR_MESSAGE,
               TEXT("FEED_BITMAP:Can't send the prolog. WSAGetLastError=%d\n"),
                    WSAGetLastError()));
        goto exitpt;
    }
    if (!RClx_SendBuffer(pClx->hSocket, 
                         &FeedBitmap, 
                         FeedProlog.HeadSize))
    {
        TRACE((ERROR_MESSAGE,
               TEXT("FEED_BITMAP:Can't send the header.\n")));
        goto exitpt;
    }
    if (!RClx_SendBuffer(pClx->hSocket, pLocalBits, FeedProlog.TailSize))
    {
        TRACE((ERROR_MESSAGE,
               TEXT("FEED_BITMAP:Can't send the bits.\n")));
        goto exitpt;
    }

    rv = TRUE;
exitpt:
    return rv;
}


/*++
 *  Function:
 *      _ClxWndProc
 *  Description:
 *      Dispatche messages procedure. Dispatches WM_TIMER and
 *      WM_WSOCK - socket message. WM_TIMER drives _OnBackground
 *      and connection retrys
 *      Win32/Win16/WinCE
 *  Arguments:
 *      hwnd        - window handle, same as g_hWindow
 *      uiMessage   - message Id
 *      wParam      - word param
 *      lParam      - long param
 *  Return value:
 *      LRESULT     - standard for window procs
 --*/
LRESULT CALLBACK LOADDS _ClxWndProc( HWND hwnd,
                              UINT uiMessage,
                              WPARAM wParam,
                              LPARAM lParam)
{
    SOCKET   hSocket;
    PCLXINFO    pClx = g_pClx;
    
    switch (uiMessage)
    {
    case WM_WSOCK:
        if (!pClx)
        {
            TRACE((WARNING_MESSAGE,
                   TEXT("Winsock message before context initialization\n")));
            goto exitpt;
        }

        hSocket = (SOCKET)wParam;
        if (hSocket != pClx->hSocket)
        {
            TRACE((WARNING_MESSAGE,
                   TEXT("Notification for unknown socket\n")));
            goto exitpt;
        }

        if (WSAGETSELECTERROR(lParam))
        {
            TRACE((WARNING_MESSAGE,
                   TEXT("Winsock error: %d\n"), WSAGETSELECTERROR(lParam)));
            RClx_Disconnect(pClx);
            goto exitpt;
        }

        if (WSAGETSELECTEVENT(lParam) == FD_CLOSE)
        {
            TRACE((INFO_MESSAGE,
                   TEXT("Connection to the test server lost\n")));
            RClx_Disconnect(pClx);
        } else if (WSAGETSELECTEVENT(lParam) == FD_READ)
        {
            if (!RClx_ReadRequest(pClx) && 
                WSAGetLastError() != WSAEWOULDBLOCK)
            {
                TRACE((WARNING_MESSAGE,
                       TEXT("Socket read error: %d\n"), WSAGetLastError()));
                RClx_Disconnect(pClx);
            }
        } else {
            TRACE((WARNING_MESSAGE,
                   TEXT("Unexpected winsock notification #%d\n"), 
                   WSAGETSELECTEVENT(lParam)));
            goto exitpt;
        }

        break;
    case WM_TIMER:    // Start connection or background process
    {
        BOOL bRunRdp = FALSE;
        HWND hConBtn, hServerBox;
#ifndef  OS_WINCE
        // no resolution box in CE
        HWND hResolutionBox;
#endif
        CHAR szResSelect[20];

        if (!pClx)
        {
            TRACE((WARNING_MESSAGE,
                   TEXT("Timer message before context initialization\n")));
            goto exitpt;
        }

        if (g_uiBackgroundTimer == (UINT_PTR)wParam)
        // This is our background thread
        {
            _OnBackground(pClx);
            goto exitpt;
        }

        if (!g_nMyReconId && !pClx->hwndDialog)
        {
            pClx->hwndDialog = _FindTopWindow(NULL,
                                              g_strClientCaption,
                                              g_hRDPInst);
            goto check_timer;
        }

        if (!g_nMyReconId && pClx->hwndDialog &&
            !GetDlgItem(pClx->hwndDialog, UI_IDC_CONNECT))
        {
            TRACE((INFO_MESSAGE, TEXT("No dialog box yet. Waiting...\n")));
            goto check_timer;
        }

        if (pClx->alive && pClx->hSocket == INVALID_SOCKET)
        {
            TRACE((INFO_MESSAGE, 
                   TEXT("Client is alive, no socket to the test server\n")));

            //_AttemptToCloseTheClient();

            goto exitpt;
        }

        // Check if we are connected
        if (pClx->hSocket == INVALID_SOCKET && (!RClx_Connect(pClx)))
                goto check_timer;

        // We are connected, send the client info
        if (!pClx->bClientInfoSent)
            RClx_SendClientInfo(pClx);

        // if we are reconnecting we don't have UI to set params
        if (g_nMyReconId)
            break;

        // Check if connect dialog is OK
        if (!pClx->hwndDialog)
            goto check_timer;

        if (!strlen(pClx->RClxInfo.szHydraServer))
        {
            TRACE((INFO_MESSAGE, 
                TEXT("Connect info is not received yet\n")));
            goto check_timer;
        }

        // set something in server listbox, to enable connect button
        {
            HWND hwndCombo = GetDlgItem(pClx->hwndDialog, UI_IDC_SERVER);

            SetDlgItemText(pClx->hwndDialog, UI_IDC_SERVER, TEXT("vladimis"));
#ifdef  OS_WIN32
            PostMessage(pClx->hwndDialog,
                        WM_COMMAND,
                        MAKEWPARAM(UI_IDC_SERVER, CBN_EDITCHANGE),
                        (LPARAM)hwndCombo);
#endif
#ifdef  OS_WIN16
            PostMessage(pClx->hwndDialog,
                        WM_COMMAND,
                        UI_IDC_SERVER,
                        MAKELONG(hwndCombo, CBN_EDITCHANGE));
#endif
        }

        // Check the connect button state
        hConBtn = GetDlgItem(pClx->hwndDialog, UI_IDC_CONNECT);

        if (!hConBtn)
        {
            TRACE((WARNING_MESSAGE, 
                TEXT("Can't get Connect button\n")));
            goto check_timer;
        }

        if (!IsWindowEnabled(hConBtn))
        {
            TRACE((INFO_MESSAGE,
                TEXT("Connect button is not enabled yet\n")));
            goto check_timer;
        }

        if (
        // Check for valid controls
           (hServerBox = GetDlgItem(pClx->hwndDialog, UI_IDC_SERVER))
#ifndef OS_WINCE
        // no resolution box in WinCE
            &&
           (hResolutionBox = GetDlgItem(pClx->hwndDialog, UI_IDC_RESOLUTION))
#endif  // !OS_WINCE
          )
        {
            TRACE((INFO_MESSAGE, 
                TEXT("The client is ready to launch.\n")));
        } else
            goto check_timer;

        bRunRdp = TRUE;

check_timer:
        if (!bRunRdp)
        {
            TRACE((INFO_MESSAGE, 
                TEXT("Can't start the client yet. Waiting\n")));
            if (!pClx->uiReconnectTimer)
                pClx->uiReconnectTimer = SetTimer(hwnd, RCLX_RECONNECT_TIMERID, 
                                                  RCLX_RECONNECTELAPSETIME, NULL);

            if (!pClx->uiReconnectTimer)
            {
                TRACE((WARNING_MESSAGE, 
                    TEXT("Can't create timer. Start timer simulation\n")));
                PostMessage(hwnd, WM_TIMER, 0, 0);
            }
            goto exitpt;
        } else {
            if (pClx->uiReconnectTimer)
            {
                KillTimer(g_hWindow, pClx->uiReconnectTimer);
                pClx->uiReconnectTimer = 0;
            }
        }

        // Check that we have clear view for launch
        // no error boxes laying arround
        if (_GarbageCollecting(pClx, TRUE))
            goto exitpt;
        
#ifdef  UNICODE
        TRACE((INFO_MESSAGE, 
               TEXT("Trying to connect to Hydra server: %S\n"),
               pClx->RClxInfo.szHydraServer));
#else
        TRACE((INFO_MESSAGE, 
               TEXT("Trying to connect to Hydra server: %s\n"),
               pClx->RClxInfo.szHydraServer));
#endif  // UNICODE

        // Set server name
#ifdef  UNICODE
        _CLX_SetDlgItemTextA
#else   // !UNICODE
        SetDlgItemText
#endif  // !UNICODE
            (pClx->hwndDialog, UI_IDC_SERVER, pClx->RClxInfo.szHydraServer);
        // Set the resolution
        _snprintf(szResSelect, sizeof(szResSelect), "%dx%d", 
                  pClx->RClxInfo.xResolution, 
                  pClx->RClxInfo.yResolution);

#ifndef  OS_WINCE
        // no resolution box
        SendMessage(hResolutionBox, CB_SELECTSTRING, 0, (LPARAM)szResSelect);
#endif  // !OS_WINCE

        // set the low speed option
        CheckDlgButton(pClx->hwndDialog,
                       UI_IDC_CONNECTION, 
                       (pClx->RClxInfo.bLowSpeed)?BST_CHECKED:BST_UNCHECKED
                      );

        // set the persistent cache option
        CheckDlgButton(pClx->hwndDialog,
                       UI_IDC_BITMAP_PERSISTENCE,
                       (pClx->RClxInfo.bPersistentCache)?BST_CHECKED
                                                        :BST_UNCHECKED
                      );

        // Now connect
        PostMessage(pClx->hwndDialog, WM_COMMAND, UI_IDC_CONNECT, 0);

    }
        break;
    case WM_CLIPBOARD:
    {
        HGLOBAL ghClipboard = NULL;
        HGLOBAL ghNewData   = NULL;
        BOOL    bOpened    = FALSE;
        BOOL    bRetry     = TRUE;
        VOID    HUGEMOD *pClipboard = NULL;
        UINT32  nSize = 0;
        UINT    uiFormat = 0;
        BOOL    bReentered;

#ifndef OS_WIN32
        pClx->bClipboardReenter++;
        bReentered = pClx->bClipboardReenter != 0;
#else
        bReentered = InterlockedIncrement(&pClx->bClipboardReenter) != 0;
#endif  // OS_WIN32

        if (bReentered)
        {
            TRACE((WARNING_MESSAGE, TEXT("WM_CLIPBOARD reentered\n")));
            bRetry = FALSE;
            goto clpcleanup;
        }

        // lParam contains the desired format
        uiFormat = (UINT)lParam;

        // WinCE version doesn't support clipboard
#ifndef OS_WINCE
        if (!OpenClipboard(hwnd))
        {
            TRACE((ERROR_MESSAGE, TEXT("Can't open the clipboard. retrying\n")));
            goto clpcleanup;
        }
        bOpened = TRUE;

        // if uiFormat is zero then return empty clipbrd body 
        // with the first available format
        if (!uiFormat)
        {
            uiFormat = EnumClipboardFormats(uiFormat);

            TRACE((INFO_MESSAGE,
                   TEXT("Responging on format request. FormatID=%d\n"),
                   uiFormat));

            goto send_clipboard;
        }

        ghClipboard = GetClipboardData((UINT)uiFormat);
        if (!ghClipboard)
        {
            TRACE((WARNING_MESSAGE, TEXT("Clipboard is empty.\n")));
        } else {
            Clp_GetClipboardData(uiFormat,
                                 ghClipboard,
                                 &nSize,
                                 &ghNewData);
            if (ghNewData)
                ghClipboard = ghNewData;

            pClipboard = GlobalLock(ghClipboard);
            if (!pClipboard)
            {
                TRACE((ERROR_MESSAGE, 
                    TEXT("Can't lock the clipboard. retrying\n")));
                goto clpcleanup;
            }
        }

send_clipboard:

#else   // !OS_WINCE
        TRACE((WARNING_MESSAGE, TEXT("WinCE: clipboard not supported\n")));
#endif  // !OS_WINCE

        if (!RClx_SendClipboard(pClx, uiFormat, nSize, pClipboard))
        {
            TRACE((ERROR_MESSAGE, TEXT("Can't send the clipboard\n")));
            bRetry = FALSE;
            goto clpcleanup;
        }

        bRetry = FALSE;

clpcleanup:
#ifndef OS_WINCE
        if (pClipboard)
            GlobalUnlock(ghClipboard);

        if (bOpened)
            CloseClipboard();

        if (ghNewData)
            GlobalFree(ghNewData);

#endif  // !OS_WINCE

#ifndef OS_WIN32
        pClx->bClipboardReenter--;
#else
        InterlockedDecrement(&pClx->bClipboardReenter);
#endif  // OS_WIN32

        if (bRetry)
        // Retry to send the clipboard
            PostMessage(hwnd, WM_CLIPBOARD, 0, lParam);
    }
        break;
    case WM_CLOSE:
        if (!DestroyWindow(hwnd))
        {
            TRACE((ERROR_MESSAGE,
                   TEXT("Can't destroy window. GetLastError: %d\n"),
                   _CLXWINDOW_CLASS,
                   GetLastError()));
        }
        break;
    default:
        return DefWindowProc(hwnd, uiMessage, wParam, lParam);
    }

exitpt:
    return 0;
}


/*++
 *  Function:
 *      RClx_CreateWindow
 *  Description:
 *      Creates g_hWindow for dispatching winsocket and timer
 *      messages
 *      Win32/Win16/WinCE
 *  Arguments:
 *      hInstance       - Dll instance handle
 *  Called by:
 *      ClxInitialize
 --*/
VOID RClx_CreateWindow(HINSTANCE hInstance)
{
    WNDCLASS    wc;
#ifdef  OS_WIN32
    DWORD       dwLastErr;
#endif

    memset(&wc, 0, sizeof(wc));

    wc.lpfnWndProc      = _ClxWndProc;
    wc.hInstance        = hInstance;
    wc.lpszClassName    = TEXT(_CLXWINDOW_CLASS);


    if (!RegisterClass (&wc) 
#ifdef  OS_WIN32
            &&
        (dwLastErr = GetLastError()) &&
        dwLastErr != ERROR_CLASS_ALREADY_EXISTS
#endif  // OS_WIN32
        )
    {
        TRACE((WARNING_MESSAGE,
              TEXT("Can't register class. GetLastError=%d\n"),
              GetLastError()));
        goto exitpt;
    }

    g_hWindow = CreateWindow(
                       TEXT(_CLXWINDOW_CLASS),
                       NULL,         // Window name
                       0,            // dwStyle
                       0,            // x
                       0,            // y
                       0,            // nWidth
                       0,            // nHeight
                       NULL,         // hWndParent
                       NULL,         // hMenu
                       hInstance,
                       NULL);        // lpParam

    if (!g_hWindow)
    {
        TRACE((WARNING_MESSAGE, 
            TEXT("Can't create window to handle socket messages\n")));
        goto exitpt;
    }

    g_uiBackgroundTimer = SetTimer(g_hWindow,
                                     RCLX_BACKGNDTIMERID,
                                     RCLX_TIMERELAPSETIME,
                                     NULL);

exitpt:
    ;
}

/*++
 *  Function:
 *      RClx_DestroyWindow
 *  Description:
 *      Destroys g_hWindow created in RClx_CreateWindow
 *      Win32/Win16/WinCE
 *  Called by:
 *      ClxTerminate
 --*/
VOID RClx_DestroyWindow(VOID)
{
    if (g_hWindow)
    {
        if (g_uiBackgroundTimer)
        {
            KillTimer(g_hWindow, g_uiBackgroundTimer);
            g_uiBackgroundTimer = 0;
        }

        PostMessage(g_hWindow, WM_CLOSE, 0, 0);

        if (!UnregisterClass(TEXT(_CLXWINDOW_CLASS), g_hInstance))
        {
            TRACE((WARNING_MESSAGE,
                   TEXT("Can't unregister class: %s. GetLastError: %d\n"),
                   _CLXWINDOW_CLASS,
                   GetLastError()));
        }
        
        g_hWindow = NULL;
    }
}


/*++
 *  Function:
 *      RClx_ReadRequest
 *  Description:
 *      CLXINFO contains a buffer for incoming requests
 *      this function trys to receive it all. If the socket blocks
 *      the function exits with OK and next time FD_READ is received will
 *      be called again. If a whole request is received it calls 
 *      RClx_ProcessRequest
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - the context
 *  Return value:
 *      TRUE    if reading doesn't fail
 *  Called by:
 *      _ClxWndProc on FD_READ event
 --*/
BOOL RClx_ReadRequest(PCLXINFO pClx)
{
    INT     recvres;
    INT     nErrorCode = 0;
    BYTE    HUGEMOD *pRecv = NULL;
    UINT32  nRecv = 0;
    BYTE    TempBuff[256];

    ASSERT(pClx);
    ASSERT(pClx->hSocket != INVALID_SOCKET);

    do {
        if (!pClx->bPrologReceived)
        {
            pRecv = (BYTE HUGEMOD *)&(pClx->RClxReqProlog) 
                                    + pClx->nBytesReceived;
            nRecv = sizeof(pClx->RClxReqProlog) - pClx->nBytesReceived;
#ifndef OS_WINCE
            recvres = recv(pClx->hSocket,
                           pRecv,
                           (int)nRecv,
                           0);
            nErrorCode = WSAGetLastError();
#else   // OS_WINCE
            recvres = AsyncRecv(pClx->hSocket,
                            pRecv,
                            nRecv,
                            &nErrorCode);
#endif  // OS_WINCE

            if (recvres != SOCKET_ERROR)
                pClx->nBytesReceived += recvres;
            else
                if (nErrorCode != WSAEWOULDBLOCK)
                  TRACE((ERROR_MESSAGE, TEXT("recv error: %d, request for 0x%lx bytes\n"),
                        nErrorCode,
                        nRecv
                        ));

            if (pClx->nBytesReceived == sizeof(pClx->RClxReqProlog))
            {
                UINT32 nReqSize = pClx->RClxReqProlog.ReqSize;

                pClx->nBytesReceived = 0;
                pClx->bPrologReceived = TRUE;

                if (nReqSize)
                // If request body is empty, don't allocate
                {
                  if (!pClx->pRequest)
retry_alloc:
                  {
                    pClx->pRequest = 
                                    _CLXALLOC(nReqSize);
                    if (pClx->pRequest)
                        pClx->nReqAllocSize = nReqSize;
                    else
                        pClx->nReqAllocSize = 0;
                  }
                  else if (nReqSize > pClx->nReqAllocSize)
                  {
                    pClx->pRequest = 
                                    _CLXREALLOC(pClx->pRequest, 
                                                nReqSize);
                    if (pClx->pRequest)
                        pClx->nReqAllocSize = nReqSize;
                    else
                    {
                        pClx->nReqAllocSize = 0;
                        goto retry_alloc;
                    }
                  }
                  if (!pClx->pRequest)
                  {
                    TRACE((WARNING_MESSAGE,     
                           TEXT("Can't alloc 0x%lx bytes for receiving a request. GetLastError=%d. Skipping\n"), 
                           nReqSize,
                           GetLastError()));
                  }
                }
            }
        } else {
            if (pClx->nBytesReceived == pClx->RClxReqProlog.ReqSize)
                goto process_req;

            if (pClx->pRequest)
            {
                pRecv = (BYTE HUGEMOD *)pClx->pRequest 
                                    + pClx->nBytesReceived;
                nRecv = pClx->RClxReqProlog.ReqSize - pClx->nBytesReceived;
            } else {
                // point to a temp buffer if pReques is not allocated
                pRecv = TempBuff;
                nRecv = sizeof(TempBuff);
            }

#ifdef  OS_WIN16
            // WFW has problems with receiving big buffers
            if (nRecv >= 0x1000)
                nRecv = 0x1000;

#endif  // OS_WIN16

#ifndef  OS_WINCE
            recvres = recv(pClx->hSocket,
                           pRecv,
                           (int)nRecv,
                           0);
            nErrorCode = WSAGetLastError();
#else   // OS_WINCE
            recvres = AsyncRecv(pClx->hSocket,
                           pRecv,
                           nRecv,
                           &nErrorCode);
#endif  // OS_WINCE

            if (recvres != SOCKET_ERROR)
            {
                pClx->nBytesReceived += recvres;
            } else {
              if (nErrorCode != WSAEWOULDBLOCK)
              {
                TRACE((ERROR_MESSAGE, 
                        TEXT("recv error: %d, request for 0x%lx bytes\n"),
                        nErrorCode,
                        nRecv
                        ));
                TRACE((ERROR_MESSAGE, 
                        TEXT("ReqProlog was received. Type=%d, Size=%d\n"),
                        pClx->RClxReqProlog.ReqType,
                        pClx->RClxReqProlog.ReqSize
                        ));
              }
            }
process_req:
            if (pClx->nBytesReceived == pClx->RClxReqProlog.ReqSize)
            {
                pClx->nBytesReceived = 0;
                pClx->bPrologReceived = FALSE;
                RClx_ProcessRequest(pClx);
            }
        }
    } while ((recvres != 0 || nRecv == 0) &&  // recvres will be 0 if nRecv is 0
              recvres != SOCKET_ERROR);

    // return FALSE if error is occured

    if (!recvres)
        return FALSE;       // connection was gracefully closed

    if (recvres == SOCKET_ERROR)
    {
        if (nErrorCode == WSAEWOULDBLOCK)
            return TRUE;    // the call will block, but ok
        else
            return FALSE;   // other SOCKET_ERROR
    }

    return TRUE;            // it is ok
}

/*++
 *  Function:
 *      RClx_DataArrived
 *  Description:
 *      Processes request for data
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - context
 *      pRClxData   - the request
 *  Called by:
 *      RClx_ProcessRequest
 --*/
VOID
RClx_DataArrived(PCLXINFO pClx, PRCLXDATA pRClxData)
{
    ASSERT(pRClxData);

    switch(pRClxData->uiType)
    {
        case DATA_BITMAP:
        {
            PREQBITMAP  pReqBitmap = (PREQBITMAP)pRClxData->Data;
            RCLXDATA    Response;
            RCLXFEEDPROLOG RespProlog;
            HANDLE      hDIB = NULL;
            DWORD       dwDIBSize = 0;

            TRACE((INFO_MESSAGE,
                    TEXT("REQDATA_BITMAP arrived\n")));

            if (pRClxData->uiSize != sizeof(*pReqBitmap))
                ASSERT(0);

            // process differently on WINCE (no shadow bitmap)
            //
            if (pClx->hdcShadowBitmap)
                _GetDIBFromBitmap(pClx->hdcShadowBitmap,
                              pClx->hShadowBitmap,
                              &hDIB,
                              (INT)pReqBitmap->left,
                              (INT)pReqBitmap->top,
                              (INT)pReqBitmap->right,
                              (INT)pReqBitmap->bottom);
            else {
                TRACE((WARNING_MESSAGE, TEXT("Shadow bitmap is NULL\n")));
            }

            if (!hDIB)
                dwDIBSize = 0;
            else
                dwDIBSize = (DWORD)GlobalSize(hDIB);

            RespProlog.FeedType = FEED_DATA;
            RespProlog.HeadSize = sizeof(Response) + dwDIBSize;
            RespProlog.TailSize = 0;

            Response.uiType = DATA_BITMAP;
            Response.uiSize = dwDIBSize;

            RClx_SendBuffer(pClx->hSocket, &RespProlog, sizeof(RespProlog));
            RClx_SendBuffer(pClx->hSocket, &Response, sizeof(Response));

            if (hDIB)
            {
                LPVOID      pDIB = GlobalLock(hDIB);
                if (pDIB)
                {
                    RClx_SendBuffer(pClx->hSocket, pDIB, dwDIBSize);
                    GlobalUnlock(hDIB);
                }
                GlobalFree(hDIB);
            }
        }
            break;
        case DATA_VC:
        {
            LPSTR   szChannelName;
            LPVOID  pData;
            DWORD   dwSize;

            szChannelName = (LPSTR)(pRClxData->Data);
            pData         = ((BYTE *)(pRClxData->Data)) + MAX_VCNAME_LEN;
            dwSize        = pRClxData->uiSize - MAX_VCNAME_LEN;

            if (pRClxData->uiSize < MAX_VCNAME_LEN)
            {
                TRACE((ERROR_MESSAGE, TEXT("DATA_VC: uiSize too small\n")));
                goto exitpt;
            }
            
            if (strlen(szChannelName) > MAX_VCNAME_LEN - 1)
            {
                TRACE((ERROR_MESSAGE, TEXT("DATA_VC: channel name too long\n")));
                goto exitpt;
            }
            _CLXSendDataVC(szChannelName, pData, dwSize);
        }
            break;
        default:
            TRACE((WARNING_MESSAGE,
                   TEXT("Unknown data request type. Ignoring\n")));
    }
exitpt:
    ;
}

/*++
 *  Function:
 *      RClx_ProcessRequest
 *  Description:
 *      Dispatches a request received by RClx_ReadRequest
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx    - context
 *  Called by:
 *      RClx_ReadRequest
 --*/
VOID RClx_ProcessRequest(PCLXINFO pClx)
{
    PRCLXMSG pClxMsg;
    DWORD    ReqType;
    HWND     hContainer;
    PRCLXREQPROLOG pReqProlog;
    LPVOID pReq;

    ASSERT(pClx);
    pReqProlog = &(pClx->RClxReqProlog);
    ASSERT(pReqProlog);
    pReq = pClx->pRequest;

    ReqType = pReqProlog->ReqType;
    switch(ReqType)
    {
        case REQ_MESSAGE:
            if (!pReq || pReqProlog->ReqSize != sizeof(*pClxMsg))
            {
                TRACE((WARNING_MESSAGE,
                       TEXT("REQ_MESSAGE with different size. Ignoring\n")));
                goto exitpt;
            }
            pClxMsg = (PRCLXMSG)pReq;

            if(!pClx->hwndInput)
            {

                hContainer = _FindWindow(pClx->hwndMain, TEXT(NAME_CONTAINERCLASS));

                if (hContainer)
                    pClx->hwndInput = _FindWindow(hContainer, TEXT(NAME_INPUT));
                else
                    pClx->hwndInput = NULL;

                if (!pClx->hwndInput)
                {
                    TRACE((WARNING_MESSAGE, 
                           TEXT("Can't find input window.")
                           TEXT(" Discarding any user input\n" )));
                    goto exitpt;
                }
            }

            SendMessage(pClx->hwndInput, 
                        (UINT)pClxMsg->message, 
                        (WPARAM)pClxMsg->wParam,
                        (LPARAM)pClxMsg->lParam);
            break;

        case REQ_CONNECTINFO:

            if (!pReq || pReqProlog->ReqSize != sizeof(RCLXCONNECTINFO))
            {
                TRACE((WARNING_MESSAGE,
                       TEXT("REQ_CONNECTINFO with different size. Ignoring\n")));
                goto exitpt;
            }
            TRACE((INFO_MESSAGE, TEXT("CONNECTINFO received\n")));

            memcpy(&pClx->RClxInfo, pReq, sizeof(pClx->RClxInfo));

            // Kick _ClxWndProc to connect
            PostMessage(g_hWindow, WM_TIMER, 0, 0);
            break;

        case REQ_GETCLIPBOARD:

            if (!pReq || pReqProlog->ReqSize != sizeof(RCLXCLIPBOARD))
            {
                TRACE((WARNING_MESSAGE,
                       TEXT("REQ_GETCLIPBOARD with different size. Ignoring\n")));

                goto exitpt;
            }

            TRACE((INFO_MESSAGE, TEXT("REQ_GETCLIPBOARD received. FormatId=%d\n"),
                ((PRCLXCLIPBOARD)pReq)->uiFormat
                ));

            // Kick _ClxWndProc to send our clipboard
            PostMessage(g_hWindow, WM_CLIPBOARD, 0, 
                (LPARAM)((PRCLXCLIPBOARD)pReq)->uiFormat);
                // lParam contains the required format

            break;

        case REQ_SETCLIPBOARD:
        {
            UINT32 nClipSize;

            if (!pReq || pReqProlog->ReqSize < sizeof(RCLXCLIPBOARD))
            {
                TRACE((WARNING_MESSAGE,
                       TEXT("REQ_SETCLIPBOARD with wrong size. Ignoring\n")));

                goto exitpt;
            }

            TRACE((INFO_MESSAGE, TEXT("REQ_SETCLIPBOARD received. FormatId=%d, Clipboard size = %d\n"), 
                    ((PRCLXCLIPBOARD)pReq)->uiFormat,
                    pReqProlog->ReqSize - sizeof(UINT)
                    ));
            nClipSize = pReqProlog->ReqSize - sizeof(((PRCLXCLIPBOARD)pReq)->uiFormat);
            _SetClipboard(
                (UINT)((PRCLXCLIPBOARD)pReq)->uiFormat,
                ((PRCLXCLIPBOARD)pReq)->pNewClipboard,
                nClipSize);
        }
            break;
        case REQ_DATA:
        {
            PRCLXDATA pRClxData = (PRCLXDATA)pReq;

            ASSERT(pRClxData);
            if (pReqProlog->ReqSize != pRClxData->uiSize + (UINT32)sizeof(*pRClxData))
                ASSERT(0);
            RClx_DataArrived(pClx, pRClxData);
        }
            break;
        default:
            TRACE((WARNING_MESSAGE,
                   TEXT("Unknown request type. Ignoring\n")));
    }

exitpt:
    ;
}


/*++
 *  Function:
 *      _EnumWindowsProc
 *  Description:
 *      Used to find a specific window
 *      Win32/Win16/WinCE
 *  Arguments:
 *      hWnd    - current enumerated window handle
 *      lParam  - pointer to SEARCHWND passed from
 *                _FindTopWindow
 *  Return value:
 *      TRUE on success but window is not found
 *      FALSE if the window is found
 *  Called by:
 *      _FindTopWindow thru EnumWindows
 --*/
BOOL CALLBACK LOADDS _EnumWindowsProc( HWND hWnd, LPARAM lParam )
{
    TCHAR    classname[128];
    TCHAR    caption[128];
    BOOL    rv = TRUE;
    _CLXWINDOWOWNER   hInst;
    PSEARCHWND pSearch = (PSEARCHWND)lParam;

    if (pSearch->szClassName && 
        !GetClassName(hWnd, classname, sizeof(classname)/sizeof(TCHAR)))
    {
        goto exitpt;
    }

    if (pSearch->szCaption && !GetWindowText(hWnd, caption, sizeof(caption)/sizeof(TCHAR)))
    {
        goto exitpt;
    }

#ifdef  OS_WINCE
    {
        DWORD procId = 0;
        GetWindowThreadProcessId(hWnd, &procId);
        hInst = procId;
    }
#else   // !OS_WINCE
#ifdef  _WIN64
    hInst = (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE);
#else   // !_WIN64
#ifdef  OS_WIN32
    hInst = (HINSTANCE)GetWindowLong(hWnd, GWL_HINSTANCE);
#endif  // OS_WIN32
#endif  // !OS_WINCE
#ifdef  OS_WIN16
    hInst = (HINSTANCE)GetWindowWord(hWnd, GWW_HINSTANCE);
#endif
#endif  // _WIN64
    if (
        (!pSearch->szClassName || !         // Check for classname
          _CLX_strcmp(classname, pSearch->szClassName)) 
    &&
        (!pSearch->szCaption || !
          _CLX_strcmp(caption, pSearch->szCaption))
    &&
        hInst == pSearch->hInstance)
    {
        ((PSEARCHWND)lParam)->hWnd = hWnd;
        rv = FALSE;
    }

exitpt:
    return rv;
}

/*++
 *  Function:
 *      _FindTopWindow
 *  Description:
 *      Find specific window by classname and/or caption and/or process Id
 *      Win32/Win16/WinCE
 *  Arguments:
 *      classname   - class name to search for, NULL ignore
 *      caption     - caption to search for, NULL ignore
 *      hInst       - instance handle, NULL ignore
 *  Return value:
 *      window handle found, NULL otherwise
 *  Called by:
 *      SCConnect, SCDisconnect, GetDisconnectResult
 --*/
HWND _FindTopWindow(LPCTSTR classname, LPCTSTR caption, _CLXWINDOWOWNER hInst)
{
    SEARCHWND search;

    search.szClassName = classname;
    search.szCaption = caption;
    search.hWnd = NULL;
    search.hInstance = hInst;

    EnumWindows(_EnumWindowsProc, (LPARAM)&search);

    return search.hWnd;
}

/*++
 *  Function:
 *      _FindWindow
 *  Description:
 *      Find child window by classname
 *      Win32/Win16/WinCE
 *  Arguments:
 *      hwndParent      - the parent window handle
 *      srchclass       - class name to search for, NULL - ignore
 *  Return value:
 *      window handle found, NULL otherwise
 *  Called by:
 *      
 --*/
HWND _FindWindow(HWND hwndParent, LPCTSTR srchclass)
{
    HWND hWnd, hwndTop, hwndNext;
    BOOL bFound;
    TCHAR classname[128];

    hWnd = NULL;

    hwndTop = GetWindow(hwndParent, GW_CHILD);
    if (!hwndTop) 
    {
        TRACE((INFO_MESSAGE, TEXT("GetWindow failed. hwnd=0x%x\n"), hwndParent));
        goto exiterr;
    }

    bFound = FALSE;
    hwndNext = hwndTop;
    do {
        hWnd = hwndNext;
        if (srchclass && !GetClassName(hWnd, classname, sizeof(classname)/sizeof(TCHAR)))
        {
            TRACE((INFO_MESSAGE, TEXT("GetClassName failed. hwnd=0x%x\n")));
            goto nextwindow;
        }

        if (!srchclass || !_CLX_strcmp(classname, srchclass))
            bFound = TRUE;
nextwindow:
#ifndef OS_WINCE
        hwndNext = GetNextWindow(hWnd, GW_HWNDNEXT);
#else   // OS_WINCE
        hwndNext = GetWindow(hWnd, GW_HWNDNEXT);
#endif  // OS_WINCE
    } while (hWnd && hwndNext != hwndTop && !bFound);

    if (!bFound) goto exiterr;

    return hWnd;
exiterr:
    return NULL;
}


/*++
 *  Function:
 *      _OnBackground
 *  Description:
 *      Simulates a background thread. Called when a message from the
 *      background timer is received
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx        - the context
 *  Called by:
 *      _ClxWndProc on WM_TIMER message from g_uiBackgroundTimer is received
 --*/
VOID _OnBackground(PCLXINFO pClx)
{
    // Try to limit this call, eats lot of the CPU
    _GarbageCollecting(pClx, TRUE);
}

/*
 * GarbageCollecting - closes all message boxes asking this and that
 * like "shall i close the connection" or "there's some error"
 * This function is forced by g_uiBackgroundTimer message
 */

/*++
 *  Function:
 *      _GarbageCollecting
 *  Description:
 *      Closes all message boxes asking this and that
 *      like "shall i close the connection" or "there's some error"
 *      This function is forced by g_uiBackgroundTimer message
 *      Win32/Win16/WinCE
 *  Arguments:
 *      pClx                - the context
 *      bNotifyForErrorBox  - if TRUE calls ClxEvent with 
 *                            "disconnected" event
 *  Return value:
 *      TRUE if error boxes are found
 *  Called by:
 *
 --*/
BOOL _GarbageCollecting(PCLXINFO pClx, BOOL bNotifyForErrorBox)
{
    HWND hBox;
    BOOL rv = FALSE;

    // Clean all extra message boxes, like saying that
    // we cannot connect because of this and that

    if (!g_hRDPInst)
        goto exitpt;

    hBox = _FindTopWindow(NULL,
                          g_strDisconnectDialogBox,
                          g_hRDPInst);
    if (hBox)
    {
        rv = TRUE;
        PostMessage(hBox, WM_CLOSE, 0, 0);

        if (bNotifyForErrorBox)
        // Notifiy that we are disconnected
            ClxEvent(pClx, CLX_EVENT_DISCONNECT, 0);
    }

    hBox = _FindTopWindow(NULL,
                          TEXT(FATAL_ERROR_5),
                          g_hRDPInst);

    if (hBox)
    {
        rv = TRUE;
        PostMessage(hBox, WM_CLOSE, 0, 0);

        if (bNotifyForErrorBox)
        // Notifiy that we are disconnected
            ClxEvent(pClx, CLX_EVENT_DISCONNECT, 0);
    }

    if (pClx->bCloseTrys)
    {
        _AttemptToCloseTheClient();
    }

exitpt:
    if (rv)
        TRACE((INFO_MESSAGE, "Error boxes found\n"));

    return rv;
}

/*++
 *  Function:
 *      _SetClipboard
 *  Description:
 *      Sets the clipboard content in RCLX mode
 *      Win32/Win16/WinCE
 *  Arguments:
 *      uiFormat    - clipboard format
 *      pClipboard  - new clipboard content
 *      nSize       - the clipboard size
 *  Called by:
 *      RClx_ProcessRequest on REQ_SETCLIPBOARD
 --*/
VOID
_SetClipboard(UINT uiFormat, PVOID pClipboard, UINT32 nSize)
{
    HGLOBAL ghNewClipboard = NULL;
    BOOL    bOpened = FALSE;
    BOOL    bFreeClipHandle = TRUE;
    LPVOID  pNewClipboard = NULL;

    // WinCE - no clipboard
#ifndef OS_WINCE
    if (!nSize)
    {
        // Just empty the clipboard
        if (OpenClipboard(NULL))
        {
            bOpened = TRUE;
            EmptyClipboard();
        } else {
            TRACE((ERROR_MESSAGE, TEXT("Can't lock the clipbord. GetLastError=%d\n"),
                  GetLastError()));
        }
        goto exitpt;
    }

    if (!pClipboard)
    {
        TRACE((ERROR_MESSAGE, TEXT("_SetClipboard: pClipboard is NULL\n")));
        goto exitpt;
    }

    ghNewClipboard = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, nSize);

    if (!ghNewClipboard)
    {
        TRACE((ERROR_MESSAGE, TEXT("Can't alloc(GlobalAlloc) %d bytes\n"),
                nSize));
        goto exitpt;
    }

    pNewClipboard = GlobalLock(ghNewClipboard);
    if (!pNewClipboard)
    {
        TRACE((ERROR_MESSAGE, TEXT("Can't lock the clipbord. GetLastError=%d\n"),
            GetLastError()));
        goto exitpt;
    }

    // Copy the data
    HUGEMEMCPY(pNewClipboard, pClipboard, nSize);

    if (!OpenClipboard(NULL))
    {
        TRACE((ERROR_MESSAGE, TEXT("Can't open the clipboard. GetLastError=%d\n"),
                GetLastError()));
        goto exitpt;
    }

    bOpened = TRUE;

    // Empty the clipboard, so we'll have only one entry
    EmptyClipboard();

    GlobalUnlock(ghNewClipboard);
    pNewClipboard = NULL;
    if (!Clp_SetClipboardData(uiFormat, 
                              ghNewClipboard, 
                              nSize, 
                              &bFreeClipHandle))
        TRACE((ERROR_MESSAGE, TEXT("SetClipboardData failed. GetLastError=%d\n"), GetLastError()));
    else
        TRACE((INFO_MESSAGE, TEXT("Clipboard is loaded successfuly. %ld bytes\n"),
                nSize));

exitpt:
    if (pNewClipboard)
        GlobalUnlock(ghNewClipboard);

    if (bOpened)
        CloseClipboard();

    // Do not free already set clipboard
    if (ghNewClipboard && bFreeClipHandle)
        GlobalFree(ghNewClipboard);

#else   // !OS_WINCE
        TRACE((WARNING_MESSAGE, TEXT("WinCE: clipboard not supported\n")));
#endif  // !OS_WINCE
}

#ifdef  OS_WINCE

SOCKET  g_hSocket = 0;
LONG    g_lEvent  = 0;
HWND    g_hNotifyWindow = NULL;
UINT    g_uiMessage = 0;
HANDLE  g_hAsyncThread = NULL;
BYTE    g_pRecvBuffer[1024];
INT     g_nRecvStart, g_nRecvLength;
BOOL    g_bGoAsync = FALSE;
CRITICAL_SECTION g_AsyncCS;
/*++
 *  Function:
 *      WSAAsyncSelect
 *  Description:
 *      Windows CE doesn't have this function. Here we are using extra thread
 *      for implementation of this mechanism
 *      WinCE only
 *  Arguments:
 *      s       - socket handle
 *      hWnd    - notification window
 *      wMsg    - message to send when event occured
 *      lEvent  - event mask, on what event this will work
 *  Return value:
 *      On error returns SOCKET_ERROR
 *  Called by:
 *      RClx_Connect, RClx_Disconnect
 --*/
INT WSAAsyncSelect (SOCKET s, HWND hWnd, UINT uiMsg, LONG lEvent)
{
    INT rv = SOCKET_ERROR;

    if (!g_hAsyncThread)
    {
        TRACE((ERROR_MESSAGE,
               TEXT("WSAAsyncSelect: no AsyncThread\n")));
        goto exitpt;
    }

    if (s == INVALID_SOCKET)
    {
        TRACE((ERROR_MESSAGE,
               TEXT("WSAAsyncSelect: INVALID_SOCKET passed\n")));
        goto exitpt;
    }

    if ((lEvent & FD_WRITE) || (lEvent & FD_CONNECT))
    {
        TRACE((ERROR_MESSAGE,
               TEXT("WSAAsyncSelec: FD_WRITE & FD_CONNECT not supported\n")));
        goto exitpt;
    }

    EnterCriticalSection(&g_AsyncCS);
    g_hSocket       = s;
    g_lEvent        = lEvent;
    g_hNotifyWindow = hWnd;
    g_uiMessage     = uiMsg;
    LeaveCriticalSection(&g_AsyncCS);

    ResumeThread(g_hAsyncThread);
    rv = 0;

exitpt:
    return rv;
}

/*++
 *  Function:
 *      AsyncRecv
 *  Description:
 *      Used to receive w/o blocking. WSAAsync Select must be called
 *      with FD_READ flag
 *      WinCE only
 *  Arguments:
 *      s               - socket handle
 *      pBuffer         - buffer for received bytes
 *      nBytesToRead    - how many bytes to receive
 *      pnErrorCode     - pointer to error code
 *  Return value:
 *      On error returns SOCKET_ERROR
 *  Called by:
 *      RClx_ReadRequest
 --*/
INT AsyncRecv(SOCKET s, PVOID pBuffer, INT nBytesToRead, INT *pnErrorCode)
{
    INT rv = SOCKET_ERROR;

    if (!g_hAsyncThread)
    {
        TRACE((ERROR_MESSAGE,
               TEXT("WSAAsyncSelect: no AsyncThread\n")));
        goto exitpt;
    }

    ASSERT(s == g_hSocket);

    (*pnErrorCode) = 0;

    EnterCriticalSection(&g_AsyncCS);
    if (!g_nRecvLength)
    {
        (*pnErrorCode) = WSAEWOULDBLOCK;
    }
    else
    {
        INT nToCopy;

        nToCopy = (nBytesToRead < g_nRecvLength)?nBytesToRead:g_nRecvLength;
        memcpy(pBuffer, g_pRecvBuffer + g_nRecvStart, nToCopy);
        rv = nToCopy;
        g_nRecvLength -= nToCopy;
        g_nRecvStart  += nToCopy;
    }
    // Resume the thread
    if (!g_nRecvLength)
        ResumeThread(g_hAsyncThread);

    LeaveCriticalSection(&g_AsyncCS);

exitpt:
    return rv;
}

/*++
 *  Function:
 *      _SelectWorker
 *  Description:
 *      Simulates WSAAsyncSelect message notification
 *      Lifetime: between ClxInitialize and ClxTerminate
 *      WinCE only
 *  Arguments:
 *      lParam  - unused parameter
 *  Return value:
 *      allways 0
 *  Called by:
 *      _StartAsyncThread as a thread function
 --*/
UINT _SelectWorker(LPVOID lpParam)
{
    SOCKET  hSocket;
    LONG    lEvent;
    FD_SET  fdRead;
    HWND    hwndNotify;
    UINT    uiMsg;
    INT     Status;

    while(g_bGoAsync)
    {
        FD_ZERO(&fdRead);

        EnterCriticalSection(&g_AsyncCS);
        hSocket = g_hSocket;
        lEvent  = g_lEvent;
        hwndNotify = g_hNotifyWindow;
        uiMsg   = g_uiMessage;
        LeaveCriticalSection(&g_AsyncCS);

        if (hSocket == INVALID_SOCKET || !lEvent || !hwndNotify || !uiMsg)
            goto wait;

        if (lEvent & FD_READ)
            FD_SET(hSocket, &fdRead);

        Status = select(-1, &fdRead, NULL, NULL, NULL);

        if (Status == SOCKET_ERROR && (lEvent & FD_CLOSE))
            PostMessage(hwndNotify, uiMsg, hSocket, FD_CLOSE);

        if (FD_ISSET(hSocket, &fdRead) && (lEvent & FD_READ))
        {
            EnterCriticalSection(&g_AsyncCS);
            if (!g_nRecvLength)
            // Read into the buffer
            {
                g_nRecvStart = 0;
                g_nRecvLength = recv(hSocket, g_pRecvBuffer, sizeof(g_pRecvBuffer), 0);
                if (g_nRecvLength == SOCKET_ERROR)
                    g_nRecvLength = 0;
            }
            LeaveCriticalSection(&g_AsyncCS);
            if (g_nRecvLength)
                PostMessage(hwndNotify, uiMsg, hSocket, FD_READ);
            else if (lEvent & FD_CLOSE)
                PostMessage(hwndNotify, uiMsg, hSocket, FD_CLOSE);
        }

wait:
        ASSERT(g_hAsyncThread);
        SuspendThread(g_hAsyncThread);
    }

    return 0;
}

/*++
 *  Function:
 *      _StartAsyncThread
 *  Description:
 *      Starts thread for simulating WSAAsyncSelect
 *      WinCE only
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      dllentry on DLL_ATTACH_PROCESS
 --*/
BOOL _StartAsyncThread(VOID)
{
    DWORD   dwThreadId;
    
    InitializeCriticalSection(&g_AsyncCS);

    g_bGoAsync = TRUE;
    g_hAsyncThread =
        CreateThread(
                NULL,      // security
                 0,         // stack size (default)
                 _SelectWorker,
                 NULL,      // parameter
                 0,         // flags
                 &dwThreadId);

    return (g_hAsyncThread != NULL);
}

/*++
 *  Function:
 *      _CloseAsyncThread
 *  Description:
 *      Destroys the thread created in _StartAsyncThread
 *      WinCE only
 *  Called by:
 *      dllentry on DLL_DETTACH_PROCESS
 --*/
VOID _CloseAsyncThread(VOID)
{
    if (g_hAsyncThread)
    {
        g_bGoAsync = FALSE;        
        ResumeThread(g_hAsyncThread);
        TRACE((INFO_MESSAGE, TEXT("Closing Async Thread\n")));
        if (WaitForSingleObject(g_hAsyncThread, 15000) == WAIT_TIMEOUT)
        {
            TRACE((WARNING_MESSAGE, 
                   TEXT("Async Thread is still alive. Retrying once more time\n")));
            ResumeThread(g_hAsyncThread);
            if (WaitForSingleObject(g_hAsyncThread, 30000) == WAIT_TIMEOUT)
            {
                TRACE((ERROR_MESSAGE,
                       TEXT("Async thread is again alive. KILL THE THREAD !!!\n")));
                TerminateThread(g_hAsyncThread, 1);
            }
        }
        g_hAsyncThread = NULL;
    }

    DeleteCriticalSection(&g_AsyncCS);
}

BOOL
CheckDlgButton(
    HWND hDlg,
    INT  nIDButton,
    UINT uCheck)
{
    LONG lres = SendDlgItemMessage(hDlg, nIDButton, BM_SETCHECK, uCheck, 0);

    return (lres == 0);
}
#endif  // OS_WINCE

#ifdef UNICODE
/*++
 *  Function:
 *      _CLX_SetDlgItemTextA
 *  Description:
 *      Ascii version for SetDlgItemText
 *      WinCE only, UNICODE only
 *  Arguments:
 *      hDlg        - dialog handle
 *      nDlgItem    - dialog item
 *      lpString    - item text
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      _ClxWndProc on WM_TIMER message
 --*/
BOOL _CLX_SetDlgItemTextA(HWND hDlg, INT nDlgItem, LPCSTR lpString)
{
    WCHAR lpStringW[128];
    INT ccLen = strlen(lpString);

    lpStringW[0] = 0;

    MultiByteToWideChar(
            CP_ACP,
            MB_ERR_INVALID_CHARS,
            lpString,
            -1,
            lpStringW,
            ccLen + 1);

    return SetDlgItemText(hDlg, nDlgItem, lpStringW);
}
#endif  // UNICODE

#ifndef OS_WINCE

/*
 *
 *  Clipboard functions
 *
 */
HGLOBAL Clp_GetMFData(HANDLE    hData,
                      UINT32    *pDataLen);
HGLOBAL Clp_SetMFData(UINT32    dataLen,
                      PVOID     pData);

// next is directly cut & paste from clputil.c
typedef struct {
    UINT32  mm;
    UINT32  xExt;
    UINT32  yExt;
} CLIPBOARD_MFPICT, *PCLIPBOARD_MFPICT;

VOID
Clp_GetClipboardData(
    UINT    format, 
    HGLOBAL hClipData, 
    UINT32  *pnClipDataSize, 
    HGLOBAL *phNewData)
{
    HGLOBAL hData   = hClipData;
    UINT32  dataLen = 0;
    WORD    numEntries;
    DWORD   dwEntries;
    PVOID   pData;

    *phNewData = NULL;
    *pnClipDataSize = 0;
    if (format == CF_PALETTE)
    {
        /****************************************************************/
        /* Find out how many entries there are in the palette and       */
        /* allocate enough memory to hold them all.                     */
        /****************************************************************/
        if (GetObject(hData, sizeof(numEntries), (LPSTR)&numEntries) == 0)
        {
            numEntries = 256;
        }

        dataLen = sizeof(LOGPALETTE) +
                               (((UINT32)numEntries - 1) * sizeof(PALETTEENTRY));

        *phNewData = GlobalAlloc(GHND, dataLen);
        if (*phNewData == 0)
        {
            TRACE((ERROR_MESSAGE, "Failed to get %d bytes for palette", dataLen));
            goto exitpt;
        }
        else
        {
            /************************************************************/
            /* now get the palette entries into the new buffer          */
            /************************************************************/
            pData = GlobalLock(*phNewData);
            dwEntries = GetPaletteEntries((HPALETTE)hData,
                                           0,
                                           numEntries,
                                           (PALETTEENTRY*)pData);
            GlobalUnlock(*phNewData);
            if (dwEntries == 0)
            {
                TRACE((ERROR_MESSAGE, "Failed to get any palette entries"));
                goto exitpt;
            }
            dataLen = (UINT32)dwEntries * sizeof(PALETTEENTRY);

        }
    } else if (format == CF_METAFILEPICT)
    {
        *phNewData = Clp_GetMFData(hData, &dataLen);
        if (!*phNewData)
        {
            TRACE((ERROR_MESSAGE, "Failed to set MF data"));
            goto exitpt;
        }
    } else {
        if (format == CF_DIB)
        {
            // Get the exact DIB size
            BITMAPINFOHEADER *pBMI = (BITMAPINFOHEADER *)GlobalLock(hData);

            if (pBMI)
            {
                if (pBMI->biSizeImage)
                    dataLen = pBMI->biSize + pBMI->biSizeImage;
                GlobalUnlock(hData);
            }
        }

        /****************************************************************/
        /* just get the length of the block                             */
        /****************************************************************/
        if (!dataLen)
            dataLen = (DWORD)GlobalSize(hData);
    }

    *pnClipDataSize = dataLen;

exitpt:
    ;
}

BOOL
Clp_SetClipboardData(
    UINT formatID, 
    HGLOBAL hClipData, 
    UINT32 nClipDataSize,
    BOOL *pbFreeHandle)
{
    BOOL            rv = FALSE;
    PVOID           pData = NULL;
    HGLOBAL         hData = NULL;
    LOGPALETTE      *pLogPalette = NULL;
    UINT            numEntries;
    UINT            memLen;

    ASSERT(pbFreeHandle);
    *pbFreeHandle = TRUE;

    if (formatID == CF_METAFILEPICT)
    {
        /********************************************************************/
        /* We have to put a handle to the metafile on the clipboard - which */
        /* means creating a metafile from the received data first           */
        /********************************************************************/
        pData = GlobalLock(hClipData);
        if (!pData)
        {
            TRACE((ERROR_MESSAGE, "Failed to lock buffer\n"));
            goto exitpt;
        }

        hData = Clp_SetMFData(nClipDataSize, pData);
        if (!hData)
        {
            TRACE((ERROR_MESSAGE, "Failed to set MF data\n"));
        }
        else if (SetClipboardData(formatID, hData) != hData)
        {
            TRACE((ERROR_MESSAGE, "SetClipboardData. GetLastError=%d\n", GetLastError()));
        }

        GlobalUnlock(hClipData);

    } else if (formatID == CF_PALETTE)
    {
        /********************************************************************/
        /* We have to put a handle to the palette on the clipboard - again  */
        /* this means creating one from the received data first             */
        /*                                                                  */
        /* Allocate memory for a LOGPALETTE structure large enough to hold  */
        /* all the PALETTE ENTRY structures, and fill it in.                */
        /********************************************************************/
        numEntries = (UINT)(nClipDataSize / sizeof(PALETTEENTRY));
        memLen     = (sizeof(LOGPALETTE) +
                                   ((numEntries - 1) * sizeof(PALETTEENTRY)));
        pLogPalette = malloc(memLen);
        if (!pLogPalette)
        {
            TRACE((ERROR_MESSAGE, "Failed to get %d bytes", memLen));
            goto exitpt;
        }

        pLogPalette->palVersion    = 0x300;
        pLogPalette->palNumEntries = (WORD)numEntries;

        /********************************************************************/
        /* get a pointer to the data and copy it to the palette             */
        /********************************************************************/
        pData = GlobalLock(hClipData);
        if (pData == NULL)
        {
            TRACE((ERROR_MESSAGE, "Failed to lock buffer"));
            goto exitpt;
        }
        HUGEMEMCPY(pLogPalette->palPalEntry, pData, nClipDataSize);

        /********************************************************************/
        /* unlock the buffer                                                */
        /********************************************************************/
        GlobalUnlock(hClipData);

        /********************************************************************/
        /* now create a palette                                             */
        /********************************************************************/
        hData = CreatePalette(pLogPalette);
        if (!hData)
        {
            TRACE((ERROR_MESSAGE, "CreatePalette failed\n"));
            goto exitpt;
        }

        /********************************************************************/
        /* and set the palette handle to the Clipboard                      */
        /********************************************************************/
        if (SetClipboardData(formatID, hData) != hData)
        {
            TRACE((ERROR_MESSAGE, "SetClipboardData. GetLastError=%d\n", GetLastError()));
        }
    } else {
        /****************************************************************/
        /* Just set it onto the clipboard                               */
        /****************************************************************/
        if (SetClipboardData(formatID, hClipData) != hClipData)
        {
            TRACE((ERROR_MESSAGE, "SetClipboardData. GetLastError=%d, hClipData=0x%x\n", GetLastError(), hClipData));
            goto exitpt;
        }

        // Only in this case we don't need to free the handle
        *pbFreeHandle = FALSE;
    }

    rv = TRUE;

exitpt:
    if (!pLogPalette)
    {
        free(pLogPalette);
    }

    return rv;
}

HGLOBAL Clp_GetMFData(HANDLE   hData,
                      UINT32   *pDataLen)
{
    UINT32          lenMFBits = 0;
    BOOL            rc        = FALSE;
    LPMETAFILEPICT  pMFP      = NULL;
    HDC             hMFDC     = NULL;
    HMETAFILE       hMF       = NULL;
    HGLOBAL         hMFBits   = NULL;
    HANDLE          hNewData  = NULL;
    CHAR            *pNewData  = NULL;
    PVOID           pBits     = NULL;

    /************************************************************************/
    /* Lock the memory to get a pointer to a METAFILEPICT header structure  */
    /* and create a METAFILEPICT DC.                                        */
    /************************************************************************/
    pMFP = (LPMETAFILEPICT)GlobalLock(hData);
    if (pMFP == NULL)
        goto exitpt;

    hMFDC = CreateMetaFile(NULL);
    if (hMFDC == NULL)
        goto exitpt;

    /************************************************************************/
    /* Copy the MFP by playing it into the DC and closing it.               */
    /************************************************************************/
    if (!PlayMetaFile(hMFDC, pMFP->hMF))
    {
        CloseMetaFile(hMFDC);
        goto exitpt;
    }
    hMF = CloseMetaFile(hMFDC);
    if (hMF == NULL)
        goto exitpt;

    /************************************************************************/
    /* Get the MF bits and determine how long they are.                     */
    /************************************************************************/
#ifdef OS_WIN16
    hMFBits   = GetMetaFileBits(hMF);
    lenMFBits = GlobalSize(hMFBits);
#else
    lenMFBits = GetMetaFileBitsEx(hMF, 0, NULL);
#endif
    if (lenMFBits == 0)
        goto exitpt;

    /************************************************************************/
    /* Work out how much memory we need and get a buffer                    */
    /************************************************************************/
    *pDataLen = sizeof(CLIPBOARD_MFPICT) + lenMFBits;
    hNewData = GlobalAlloc(GHND, *pDataLen);
    if (hNewData == NULL)
        goto exitpt;

    pNewData = GlobalLock(hNewData);

    /************************************************************************/
    /* Copy the MF header and bits into the buffer.                         */
    /************************************************************************/
    ((PCLIPBOARD_MFPICT)pNewData)->mm   = pMFP->mm;
    ((PCLIPBOARD_MFPICT)pNewData)->xExt = pMFP->xExt;
    ((PCLIPBOARD_MFPICT)pNewData)->yExt = pMFP->yExt;

#ifdef OS_WIN16
    pBits = GlobalLock(hMFBits);
    HUGEMEMCPY((pNewData + sizeof(CLIPBOARD_MFPICT)),
              pBits,
              lenMFBits);
    GlobalUnlock(hMFBits);
#else
    lenMFBits = GetMetaFileBitsEx(hMF, lenMFBits,
                                  (pNewData + sizeof(CLIPBOARD_MFPICT)));
    if (lenMFBits == 0)
        goto exitpt;
#endif

    /************************************************************************/
    /* all OK                                                               */
    /************************************************************************/
    rc = TRUE;

exitpt:
    /************************************************************************/
    /* Unlock any global mem.                                               */
    /************************************************************************/
    if (pMFP)
    {
        GlobalUnlock(hData);
    }
    if (pNewData)
    {
        GlobalUnlock(hNewData);
    }

    /************************************************************************/
    /* if things went wrong, then free the new data                         */
    /************************************************************************/
    if ((rc == FALSE) && (hNewData != NULL))
    {
        GlobalFree(hNewData);
        hNewData = NULL;
    }

    return(hNewData);

}


HGLOBAL Clp_SetMFData(UINT32 dataLen,
                      PVOID  pData)
{
    BOOL           rc           = FALSE;
    HGLOBAL        hMFBits      = NULL;
    PVOID          pMFMem       = NULL;
    HMETAFILE      hMF          = NULL;
    HGLOBAL        hMFPict      = NULL;
    LPMETAFILEPICT pMFPict      = NULL;

    /************************************************************************/
    /* Allocate memory to hold the MF bits (we need the handle to pass to   */
    /* SetMetaFileBits).                                                    */
    /************************************************************************/
    hMFBits = GlobalAlloc(GHND, dataLen - sizeof(CLIPBOARD_MFPICT));
    if (hMFBits == NULL)
        goto exitpt;

    /************************************************************************/
    /* Lock the handle and copy in the MF header.                           */
    /************************************************************************/
    pMFMem = GlobalLock(hMFBits);
    if (pMFMem == NULL)
        goto exitpt;

    HUGEMEMCPY(pMFMem,
           (PVOID)((CHAR *)pData + sizeof(CLIPBOARD_MFPICT)),
               dataLen - sizeof(CLIPBOARD_MFPICT) );

    GlobalUnlock(hMFBits);

    /************************************************************************/
    /* Now use the copied MF bits to create the actual MF bits and get a    */
    /* handle to the MF.                                                    */
    /************************************************************************/
#ifdef OS_WIN16
    hMF = SetMetaFileBits(hMFBits);
#else
    hMF = SetMetaFileBitsEx(dataLen - sizeof(CLIPBOARD_MFPICT), pMFMem);
#endif
    if (hMF == NULL)
        goto exitpt;

    /************************************************************************/
    /* Allocate a new METAFILEPICT structure, and use the data from the     */
    /* header.                                                              */
    /************************************************************************/
    hMFPict = GlobalAlloc(GHND, sizeof(METAFILEPICT));
    pMFPict = (LPMETAFILEPICT)GlobalLock(hMFPict);
    if (!pMFPict)
        goto exitpt;

    pMFPict->mm   = (int)((PCLIPBOARD_MFPICT)pData)->mm;
    pMFPict->xExt = (int)((PCLIPBOARD_MFPICT)pData)->xExt;
    pMFPict->yExt = (int)((PCLIPBOARD_MFPICT)pData)->yExt;
    pMFPict->hMF  = hMF;

    GlobalUnlock(hMFPict);

    rc = TRUE;

exitpt:
    /************************************************************************/
    /* tidy up                                                              */
    /************************************************************************/
    if (!rc)
    {
        if (hMFPict)
        {
            GlobalFree(hMFPict);
        }
        if (hMFBits)
        {
            GlobalFree(hMFBits);
        }
    }

    return(hMFPict);

}
#endif  // !OS_WINCE

BOOL
WS_Init(VOID)
{
    WORD    versionRequested;
    WSADATA wsaData;
    INT     intRC;
    BOOL    rv = FALSE;

    versionRequested = MAKEWORD(1, 1);

    intRC = WSAStartup(versionRequested, &wsaData);

    if (intRC != 0)
        goto exitpt;

    rv = TRUE;

exitpt:
    return rv;
}

VOID
_AttemptToCloseTheClient(VOID)
{
    HWND hYesNo = NULL;
    static BOOL bSpeedupTimer = FALSE;

    if (!bSpeedupTimer)
    {
        KillTimer(g_hWindow, g_uiBackgroundTimer);
        g_uiBackgroundTimer = SetTimer(g_hWindow,
                                     RCLX_BACKGNDTIMERID,
                                     RCLX_TIMERELAPSETIME/15+1000,
                                     NULL);

        bSpeedupTimer = TRUE;
    }



    hYesNo = _FindTopWindow(NULL,
                            g_strYesNoShutdown,
                            g_hRDPInst);

    if (hYesNo)
    {
        PostMessage(hYesNo, WM_KEYDOWN, VK_RETURN, 0);
    } else {
        PostMessage(g_pClx->hwndMain, WM_CLOSE, 0, 0);
        // Don't know how, but this helps for NT4 client
        PostMessage(g_pClx->hwndMain, WM_LBUTTONDOWN, 0, 0);
    }

}

/*
 *  This came from: \\index1\src\nt\private\samples\wincap32\dibutil.c
 */

#define WIDTHBYTES(bits)    (((bits) + 31) / 32 * 4)
#define IS_WIN30_DIB(lpbi)  ((*(LPDWORD)(lpbi)) == sizeof(BITMAPINFOHEADER))

WORD DIBNumColors(LPSTR lpDIB)
{
    WORD wBitCount;  // DIB bit count

    // If this is a Windows-style DIB, the number of colors in the
    // color table can be less than the number of bits per pixel
    // allows for (i.e. lpbi->biClrUsed can be set to some value).
    // If this is the case, return the appropriate value.


    if (IS_WIN30_DIB(lpDIB))
    {
        DWORD dwClrUsed;

        dwClrUsed = ((LPBITMAPINFOHEADER)lpDIB)->biClrUsed;
        if (dwClrUsed)

        return (WORD)dwClrUsed;
    }

    // Calculate the number of colors in the color table based on
    // the number of bits per pixel for the DIB.

    if (IS_WIN30_DIB(lpDIB))
        wBitCount = ((LPBITMAPINFOHEADER)lpDIB)->biBitCount;
    else
        wBitCount = ((LPBITMAPCOREHEADER)lpDIB)->bcBitCount;

    // return number of colors based on bits per pixel

    switch (wBitCount)
    {
        case 1:
            return 2;

        case 4:
            return 16;

        case 8:
            return 256;

        default:
            return 0;
    }
}

WORD PaletteSize(LPSTR lpDIB)
{
    // calculate the size required by the palette
    if (IS_WIN30_DIB (lpDIB))
        return (DIBNumColors(lpDIB) * sizeof(RGBQUAD));
    else
        return (DIBNumColors(lpDIB) * sizeof(RGBTRIPLE));
}

/*************************************************************************
 *
 * BitmapToDIB()
 *
 * Parameters:
 *
 * HBITMAP hBitmap  - specifies the bitmap to convert
 *
 * HPALETTE hPal    - specifies the palette to use with the bitmap
 *
 * Return Value:
 *
 * HANDLE           - identifies the device-dependent bitmap
 *
 * Description:
 *
 * This function creates a DIB from a bitmap using the specified palette.
 *
 ************************************************************************/

HANDLE BitmapToDIB(HBITMAP hBitmap, HPALETTE hPal)
{
    BITMAP              bm;         // bitmap structure
    BITMAPINFOHEADER    bi;         // bitmap header
    LPBITMAPINFOHEADER  lpbi;       // pointer to BITMAPINFOHEADER
    DWORD               dwLen;      // size of memory block
    HANDLE              hDIB, h;    // handle to DIB, temp handle
    HDC                 hDC;        // handle to DC
    WORD                biBits;     // bits per pixel

    // check if bitmap handle is valid

    if (!hBitmap)
        return NULL;

    // fill in BITMAP structure, return NULL if it didn't work

    if (!GetObject(hBitmap, sizeof(bm), (LPSTR)&bm))
        return NULL;

    // if no palette is specified, use default palette

    if (hPal == NULL)
        hPal = GetStockObject(DEFAULT_PALETTE);

    // calculate bits per pixel

    biBits = bm.bmPlanes * bm.bmBitsPixel;

    // make sure bits per pixel is valid

    if (biBits <= 1)
        biBits = 1;
    else if (biBits <= 4)
        biBits = 4;
    else if (biBits <= 8)
        biBits = 8;
    else // if greater than 8-bit, force to 24-bit
        biBits = 24;

    // initialize BITMAPINFOHEADER

    bi.biSize = sizeof(BITMAPINFOHEADER);
    bi.biWidth = bm.bmWidth;
    bi.biHeight = bm.bmHeight;
    bi.biPlanes = 1;
    bi.biBitCount = biBits;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0;
    bi.biXPelsPerMeter = 0;
    bi.biYPelsPerMeter = 0;
    bi.biClrUsed = 0;
    bi.biClrImportant = 0;

    // calculate size of memory block required to store BITMAPINFO

    dwLen = bi.biSize + PaletteSize((LPSTR)&bi);

    // get a DC

    hDC = GetDC(NULL);

    if ( !hDC )
        return NULL;

    // select and realize our palette

    hPal = SelectPalette(hDC, hPal, FALSE);
    RealizePalette(hDC);

    // alloc memory block to store our bitmap

    hDIB = GlobalAlloc(GHND, dwLen);

    // if we couldn't get memory block

    if (!hDIB)
    {
      // clean up and return NULL

      SelectPalette(hDC, hPal, TRUE);
      RealizePalette(hDC);
      ReleaseDC(NULL, hDC);
      return NULL;
    }

    // lock memory and get pointer to it

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    /// use our bitmap info. to fill BITMAPINFOHEADER

    *lpbi = bi;

    // call GetDIBits with a NULL lpBits param, so it will calculate the
    // biSizeImage field for us    

    GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, NULL, (LPBITMAPINFO)lpbi,
        DIB_RGB_COLORS);

    // get the info. returned by GetDIBits and unlock memory block

    bi = *lpbi;
    GlobalUnlock(hDIB);

    // if the driver did not fill in the biSizeImage field, make one up 
    if (bi.biSizeImage == 0)
        bi.biSizeImage = WIDTHBYTES((DWORD)bm.bmWidth * biBits) * bm.bmHeight;

    // realloc the buffer big enough to hold all the bits

    dwLen = bi.biSize + PaletteSize((LPSTR)&bi) + bi.biSizeImage;

    if (h = GlobalReAlloc(hDIB, dwLen, 0))
        hDIB = h;
    else
    {
        // clean up and return NULL

        GlobalFree(hDIB);
        hDIB = NULL;
        SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
        return NULL;
    }

    // lock memory block and get pointer to it */

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDIB);

    // call GetDIBits with a NON-NULL lpBits param, and actualy get the
    // bits this time

    if (GetDIBits(hDC, hBitmap, 0, (UINT)bi.biHeight, (LPSTR)lpbi +
            (WORD)lpbi->biSize + PaletteSize((LPSTR)lpbi), (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS) == 0)
    {
        // clean up and return NULL

        GlobalUnlock(hDIB);
        hDIB = NULL;
        SelectPalette(hDC, hPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(NULL, hDC);
        return NULL;
    }

    bi = *lpbi;

    // clean up 
    GlobalUnlock(hDIB);
    SelectPalette(hDC, hPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(NULL, hDC);

    // return handle to the DIB
    return hDIB;
}

/*++
 *  Function:
 *      _GetDIBFromBitmap
 *  Description:
 *      Copies a rectangle from hBitmap and converts it to DIB data
 *
 *  Arguments:
 *      hBitmap - the main bitmap
 *      ppDIB   - pointer to DIB data
 *      left, top, right, bottom - describes the rectangle
 *                               - if all are == -1, returns the whole bitmap
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      _ClxWndProc on WM_TIMER message
 --*/
VOID
_GetDIBFromBitmap(
    HDC     hdcMemSrc,
    HBITMAP hBitmap,
    HANDLE  *phDIB,
    INT     left,
    INT     top,
    INT     right,
    INT     bottom)
{
    HANDLE  hDIB = NULL;

    HDC     hdcMemDst = NULL;
    HDC     hdcScreen = NULL;
    HBITMAP hDstBitmap = NULL;
    HBITMAP hOldDstBmp = NULL;

    if (!hdcMemSrc)
        goto exitpt;

    if (left == -1 && right == -1 && top == -1 && bottom == -1 && hBitmap)
    {
        BITMAP bitmapInfo; 

        if (sizeof(bitmapInfo) != 
                GetObject(hBitmap, sizeof(bitmapInfo), &bitmapInfo))
            goto exitpt;

        left    = top = 0;
        right   = bitmapInfo.bmWidth;
        bottom  = bitmapInfo.bmHeight;
    }

    // reorder left...bottom if needed
    if (left > right)
    {
        INT change = left;
        left = right;
        right = change;
    }

    if (top > bottom)
    {
        INT change = top;
        top = bottom;
        bottom = change;
    }

    hdcScreen = GetDC(NULL);
    hdcMemDst = CreateCompatibleDC(hdcScreen);
    hDstBitmap = CreateCompatibleBitmap(hdcScreen, right - left, bottom - top);

    if (!hdcMemDst || !hDstBitmap)
    {
        TRACE(( ERROR_MESSAGE, TEXT("Can't create destination DC to get client's DIB\n")));
        goto exitpt;
    }
    
    hOldDstBmp = SelectObject(hdcMemDst, hDstBitmap);

    if (!BitBlt( hdcMemDst,
            0, 0,   // dest x,y
            right - left,   // dest width
            bottom - top,   // dest height
            hdcMemSrc,
            left, top,     // source coordinates
            SRCCOPY))
        goto exitpt;

    TRACE((INFO_MESSAGE, TEXT("Getting DIB (%d, %d, %d, %d)\n"), 
            left, top, right, bottom));

    hDIB = BitmapToDIB(hDstBitmap, NULL); 

exitpt:
    if (hdcMemDst)
    {
        if (hOldDstBmp)
            SelectObject(hdcMemDst, hOldDstBmp);

        DeleteDC(hdcMemDst);
    }

    if (hdcScreen)
        ReleaseDC(NULL, hdcScreen);

    if (hDstBitmap)
        DeleteObject(hDstBitmap);

    *phDIB      = hDIB;

    if (!hDIB)
        TRACE((ERROR_MESSAGE, TEXT("Can't get client's DIB. GetLastError=%d\n"), GetLastError()));

}

#ifndef OS_WINCE
#ifdef  OS_WIN32
DWORD
__stdcall
_ClxSendMsgThread(VOID *param)
{
    while(1)
    {
        if (!g_pClx || WaitForSingleObject(g_pClx->semSendReady, INFINITE) !=
            WAIT_OBJECT_0)
                goto exitpt;

        if (!g_pClx || g_pClx->bSendMsgThreadExit)
            goto exitpt;

        SendMessage(g_pClx->msg.hwnd,
                    g_pClx->msg.message,
                    g_pClx->msg.wParam,
                    g_pClx->msg.lParam);

        // release the owner of the message
        ReleaseSemaphore(g_pClx->semSendCompleted, 1, NULL);
        // release next waiting worker
        ReleaseSemaphore(g_pClx->semSendDone, 1, NULL);
    }

exitpt:
    return 0;
}

/*++
 *  Function:
 *      _ClxSendMessage
 *  Description:
 *      Calls SendMessage from separate thread
 *      prevents deadlock on SendMessage (#319816)
 *
 *  Arguments:
 *      hBitmap - the main bitmap
 *      ppDIB   - pointer to DIB data
 *      left, top, right, bottom - describes the rectangle
 *                               - if all are == -1, returns the whole bitmap
 *  Return value:
 *      TRUE on success
 *  Called by:
 *      _ClxWndProc on WM_TIMER message
 --*/
LRESULT
_ClxSendMessage(
  HWND hWnd,      // handle of destination window
  UINT Msg,       // message to send
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
)
{
    LRESULT  rv = 0;
    PCLXINFO pClx = g_pClx;
    DWORD    dwThreadId;

    if (!pClx)
        goto exitpt;

    if (!pClx->semSendDone)
        pClx->semSendDone = CreateSemaphore(NULL, 1, 10, NULL);
    if (!pClx->semSendReady)
        pClx->semSendReady = CreateSemaphore(NULL, 0, 10, NULL);
    if (!pClx->semSendCompleted)
        pClx->semSendCompleted = CreateSemaphore(NULL, 0, 10, NULL);

    if (!pClx->semSendDone || !pClx->semSendReady || !pClx->semSendCompleted)
        goto exitpt;

    if (!pClx->hSendMsgThread)
    {
        pClx->hSendMsgThread = CreateThread(
                NULL,
                0,
                _ClxSendMsgThread, 
                NULL,
                0, 
                &dwThreadId);
    }

    if (!pClx->hSendMsgThread)
        goto exitpt;

    // Wait 10 mins send to complete
    if (WaitForSingleObject(pClx->semSendDone, 600000) !=
        WAIT_OBJECT_0)
        goto exitpt;

    pClx->msg.hwnd = hWnd;
    pClx->msg.message = Msg;
    pClx->msg.wParam = wParam;
    pClx->msg.lParam = lParam;

    // Signal the thread for available message
    ReleaseSemaphore(pClx->semSendReady, 1, NULL);

    // Wait for the send to complete
    WaitForSingleObject(pClx->semSendCompleted, 600000);

exitpt:
    return rv;
}
 
VOID
_ClxDestroySendMsgThread(PCLXINFO pClx)
{
    if (!pClx)
        goto exitpt;

    if (!pClx->semSendDone || !pClx->semSendReady || !pClx->hSendMsgThread ||
        !pClx->semSendCompleted)
        goto exitpt;

    // Wait 10 mins send to complete
    WaitForSingleObject(pClx->semSendDone, 600000);

    pClx->bSendMsgThreadExit = TRUE;

    // signal the thread to exit
    ReleaseSemaphore(pClx->semSendReady, 1, NULL);
    
    // wait for the thread to exit
    if (WaitForSingleObject(pClx->hSendMsgThread, 1200000) != WAIT_OBJECT_0)
    {
        TRACE((ERROR_MESSAGE, TEXT("SendThread can't exit, calling TerminateThread\n")));
        TerminateThread(pClx->hSendMsgThread, 0);
    }
    CloseHandle(pClx->hSendMsgThread);
exitpt:

    if (pClx->semSendCompleted)
    {
        CloseHandle(pClx->semSendCompleted);
        pClx->semSendCompleted = NULL;
    }

    if (pClx->semSendDone)
    {
        CloseHandle(pClx->semSendDone);
        pClx->semSendDone = NULL;
    }

    if (pClx->semSendReady)
    {
        CloseHandle(pClx->semSendReady);
        pClx->semSendReady = NULL;
    }

    pClx->hSendMsgThread = 0;

    ;
}

#endif  // OS_WIN32
#endif  // !OS_WINCE

//////////////////////////////////////////////////////////////////////
//
//  VC Channel support for RCLX mode
//

DWORD
CLXAPI
CLXDataReceivedVC(
    LPCSTR  szChannelName,
    LPVOID  pData,
    DWORD   dwSize
    )
{
    DWORD    rv = (DWORD)-1;
    PCLXINFO pClx;
    CHAR     szName[MAX_VCNAME_LEN];
    PCLXVCHANNEL pVChannel;
    RCLXDATA    Response;
    RCLXFEEDPROLOG  Prolog;
    BOOL rc;

    // Check if this channel is already registered
    pVChannel = g_pVChannels;
    // find the channel entry
    while(pVChannel && _stricmp(pVChannel->szName, szChannelName))
    {
        pVChannel = pVChannel->pNext;
    }

    if (!pVChannel)
    {
        TRACE((WARNING_MESSAGE, TEXT("Channel %s is not registered\n"), szChannelName));
        goto exitpt;
    }

    if (!IS_RCLX)
    {
        TRACE((WARNING_MESSAGE, TEXT("CLXDataReceivedVC: not in RCLX mode\n")));
        goto exitpt;
    }

    if (!g_pClx)
    {
        TRACE((ERROR_MESSAGE, TEXT("CLXDataReceivedVC: pClx is NULL\n")));
        goto exitpt;
    }

    if (strlen(szChannelName) > MAX_VCNAME_LEN - 1)
    {
        TRACE((ERROR_MESSAGE, TEXT("Channel name \"%s\"bigger than %d chars\n"),
            szChannelName, MAX_VCNAME_LEN));
        goto exitpt;
    }

    pClx = g_pClx;

    Prolog.FeedType = FEED_DATA;
    Prolog.HeadSize = sizeof(Response) + sizeof(szName) + dwSize;
    Prolog.TailSize = 0;

    Response.uiType = DATA_VC;
    Response.uiSize = sizeof(szName) + dwSize;

    strcpy(szName, szChannelName);

    TRACE((ALIVE_MESSAGE, 
            TEXT("Sending VC data, DataSize=%d, HeadSize=%d, TailSize=%d, Name=%s\n"),
            dwSize,
            Prolog.HeadSize,
            Prolog.TailSize,
            szName));
    rc =        RClx_SendBuffer(pClx->hSocket, &Prolog, sizeof(Prolog));
    rc = rc &&  RClx_SendBuffer(pClx->hSocket, &Response, sizeof(Response));
    rc = rc &&  RClx_SendBuffer(pClx->hSocket, szName, sizeof(szName));
    rc = rc &&  RClx_SendBuffer(pClx->hSocket, pData, dwSize);

    if (!rc)
    {
        TRACE((WARNING_MESSAGE, TEXT("Unable to sent message\n")));
        goto exitpt;
    }
    rv = 0;

exitpt:
    return rv;
}

DWORD
_CLXSendDataVC(
    LPCSTR szChannelName,
    LPVOID pData,
    DWORD  dwSize
    )
{
    DWORD rv = (DWORD)-1;
    // Check if this channel is already registered
    PCLXVCHANNEL pVChannel = g_pVChannels;

    // find the channel entry
    while(pVChannel && _stricmp(pVChannel->szName, szChannelName))
    {
        pVChannel = pVChannel->pNext;
    }

    if (!pVChannel)
    {
        TRACE((WARNING_MESSAGE, TEXT("Channel %s is not registered\n"), szChannelName));
        goto exitpt;
    }
    
    ASSERT(pVChannel->pSendDataFn);

    rv = (pVChannel->pSendDataFn)(pData, dwSize);

exitpt:
    return rv;
}

BOOL
CLXAPI
ClxRegisterVC(
    LPCSTR              szChannelName,
    PCLXVC_SENDDATA     pSendData,
    PCLXVC_RECVDATA     *ppRecvData
    )
{
    BOOL rv = FALSE;
    PCLXVCHANNEL pVChannel;
    UINT counter;

    if (!szChannelName || !pSendData || !ppRecvData ||
        strlen(szChannelName) > MAX_VCNAME_LEN - 1)
    {
        TRACE((ERROR_MESSAGE, TEXT("ClxRegisterVC: invalid parameters\n")));
        goto exitpt;
    }

    pVChannel = malloc(sizeof(*pVChannel));
    if (!pVChannel)
        goto exitpt;

    strcpy(pVChannel->szName, szChannelName);

    // zero the rest of the name
    for(counter = strlen(szChannelName); counter < MAX_VCNAME_LEN; counter++)
        pVChannel->szName[counter] = 0;

    pVChannel->pSendDataFn = pSendData;

    *ppRecvData = CLXDataReceivedVC;
    
    // Push this in the queue
    pVChannel->pNext = g_pVChannels;
    g_pVChannels = pVChannel;
        
    rv = TRUE;

exitpt:
    return rv;
}

VOID
CLXAPI
ClxUnregisterVC(
    LPCSTR  szChannelName
    )
{
    PCLXVCHANNEL pVChannel = g_pVChannels;
    PCLXVCHANNEL pPrev = NULL;

    // find the channel and remove it from the queue
    while(pVChannel && _stricmp(pVChannel->szName, szChannelName))
    {
        pPrev = pVChannel;
        pVChannel = pVChannel->pNext;
    }

    if (!pVChannel)
    {
        TRACE((WARNING_MESSAGE, TEXT("Can't find channel name: %s\n"), 
                szChannelName));
        goto exitpt;
    }

    if (!pPrev)
        g_pVChannels = pVChannel->pNext;
    else
        pPrev->pNext = pVChannel->pNext;

exitpt:
    ;
}

//////////////////////////////////////////////////////////////////////