/**************************************************************************\
* Module Name: context.c
*
* Copyright (c) Microsoft Corp. 1995 All Rights Reserved
*
* Context management routines for imm32 dll
*
* History:
* 03-Jan-1996 wkwok       Created
\**************************************************************************/

#include "precomp.h"
#pragma hdrstop

#define IMCC_ALLOC_TOOLARGE             0x1000


/***************************************************************************\
* ImmCreateDefaultContext (Internal: Callback from Win32K.SYS)
*
* Create client side input context sturcture for a given hImc
*
* History:
* 31-Jan-1996 wkwok       Created
\***************************************************************************/

BOOL WINAPI ImmCreateDefaultContext(
    HIMC hImc)
{
    PCLIENTIMC pClientImc;

    /*
     * ImmLockClientImc() will create client side Imc.
     */
    pClientImc = ImmLockClientImc(hImc);
    if (pClientImc != NULL) {
        /*
         * Marks with default input context signature.
         */
        SetICF(pClientImc, IMCF_DEFAULTIMC);
        ImmUnlockClientImc(pClientImc);
    }

    /*
     * Load up the IME DLL of current keyboard layout.
     */
    ImmLoadIME(GetKeyboardLayout(0));

    return (pClientImc != NULL);
}


/**************************************************************************\
* ImmCreateContext
*
* Creates and initializes an input context.
*
* 17-Jan-1996 wkwok       Created
\**************************************************************************/

HIMC WINAPI ImmCreateContext(void)
{
    PCLIENTIMC pClientImc;
    HIMC       hImc = NULL_HIMC;

    pClientImc = ImmLocalAlloc(HEAP_ZERO_MEMORY, sizeof(CLIENTIMC));

    if (pClientImc != NULL) {

        hImc = NtUserCreateInputContext((DWORD)pClientImc);
        if (hImc == NULL_HIMC) {
            ImmLocalFree(pClientImc);
            return NULL_HIMC;
        }

        InitImcCrit(pClientImc);
        pClientImc->hImc = hImc;
    }

    return hImc;
}


/**************************************************************************\
* ImmDestroyContext
*
* Destroys an input context.
*
* 17-Jan-1996 wkwok       Created
\**************************************************************************/

BOOL WINAPI ImmDestroyContext(
    HIMC hImc)
{
    return DestroyInputContext(hImc, GetKeyboardLayout(0));
}


/**************************************************************************\
* ImmAssociateContext
*
* Associates an input context to the specified window handle.
*
* 17-Jan-1996 wkwok       Created
\**************************************************************************/

HIMC WINAPI ImmAssociateContext(
    HWND hWnd,
    HIMC hImc)
{
    PWND  pWnd;
    HIMC  hPrevImc, hDefImc;
    PINPUTCONTEXT pInputContext;

    if ((pWnd = ValidateHwnd(hWnd)) == (PWND)NULL) {
        RIPMSG1(RIP_WARNING,
              "ImmAssociateContext: invalid window handle %x", hWnd);
        return NULL_HIMC;
    }

    /*
     * associate to the same input context, do nothing.
     */
    if (pWnd->hImc == hImc)
        return hImc;

    hPrevImc = NtUserAssociateInputContext(hWnd, hImc);
    if (hPrevImc == NULL_HIMC)
        return hPrevImc;

    hDefImc = (HIMC)NtUserQueryWindow(hWnd, WindowDefaultInputContext);

    /*
     * Update the hPrevImc if it is not the default IMC.
     */
    if (hDefImc != hPrevImc) {
        pInputContext = ImmLockIMC(hPrevImc);
        if (pInputContext != NULL) {
            pInputContext->hWnd = NULL;
            ImmUnlockIMC(hPrevImc);
        }
    }

    if (GetFocus() == hWnd) {
        ImmSetActiveContext(hWnd, hPrevImc, FALSE);
        ImmSetActiveContext(hWnd, hImc, TRUE);
    }

    return hPrevImc;
}


/**************************************************************************\
* ImmGetContext
*
* Retrieves the input context that is associated to the given window.
*
* 17-Jan-1996 wkwok       Created
\**************************************************************************/

HIMC WINAPI ImmGetContext(
    HWND hWnd)
{
    PWND  pwnd;

    if ((pwnd = ValidateHwnd(hWnd)) == (PWND)NULL) {
        RIPMSG1(RIP_WARNING,
              "ImmGetContext: invalid window handle %x", hWnd);
        return NULL_HIMC;
    }

    /*
     * Don't allow other process to access input context
     */
    if (!TestWindowProcess(pwnd)) {
        RIPMSG0(RIP_WARNING,
              "ImmGetContext: can not get input context of other process");
        return NULL_HIMC;
    }

    return pwnd->hImc;
}


/**************************************************************************\
* ImmGetSaveContext
*
* Retrieves the input context that is associated to the given window.
*
* 15-Mar-1996 wkwok       Created
\**************************************************************************/

HIMC ImmGetSaveContext(
    HWND  hWnd,
    DWORD dwFlag)
{
    HIMC       hRetImc;
    PCLIENTIMC pClientImc;

    if (hWnd == NULL) {
        /*
         * Retrieves the default input context of current thread.
         */
        hRetImc = NtUserGetThreadState(UserThreadStateDefaultInputContext);
    }
    else {
        /*
         * Retrieves the input context associated to the given window.
         */
        hRetImc = ImmGetContext(hWnd);
        if (hRetImc == NULL_HIMC && (dwFlag & IGSC_DEFIMCFALLBACK)) {
            /*
             * hWnd associated with NULL input context, retrieves the
             * default input context of the hWnd's creator thread.
             */
            hRetImc = (HIMC)NtUserQueryWindow(hWnd, WindowDefaultInputContext);
        }
    }

    pClientImc = ImmLockClientImc(hRetImc);
    if (pClientImc == NULL)
        return NULL_HIMC;

    if ((dwFlag & IGSC_WINNLSCHECK) && TestICF(pClientImc, IMCF_WINNLSDISABLE))
        hRetImc = NULL_HIMC;

    ImmUnlockClientImc(pClientImc);

    return hRetImc;    
}


/**************************************************************************\
* ImmReleaseContext
*
* Releases the input context retrieved by ImmGetContext().
*
* 17-Jan-1996 wkwok       Created
\**************************************************************************/

BOOL WINAPI ImmReleaseContext(
    HWND hWnd,
    HIMC hImc)
{
   return TRUE;
}


/**************************************************************************\
* ImmSetActiveContext
*
* 15-Mar-1996 wkwok       Created
\**************************************************************************/

BOOL ImmSetActiveContext(
    HWND hWnd,
    HIMC hImc,
    BOOL fActivate)
{
    PCLIENTIMC    pClientImc;
    PINPUTCONTEXT pInputContext;
    PIMEDPI       pImeDpi;
    DWORD         dwISC;
    HIMC          hSaveImc;
    HWND          hDefImeWnd;
#ifdef DEBUG
    PWND          pWnd = ValidateHwnd(hWnd);

    if (pWnd != NULL && GETPTI(pWnd) != PtiCurrent()) {
        RIPMSG1(RIP_WARNING, "hWnd (=%lx) is not of current thread.", hWnd);
    }
#endif

    dwISC = ISC_SHOWUIALL;

    pClientImc = ImmLockClientImc(hImc);

    if (!fActivate) {
        if (pClientImc != NULL)
            ClrICF(pClientImc, IMCF_ACTIVE);
        goto NotifySetActive;
    }

    if (hImc == NULL_HIMC) {
        hSaveImc = ImmGetSaveContext(hWnd, 0);
        pInputContext = ImmLockIMC(hSaveImc);
        if (pInputContext != NULL) {
            pInputContext->hWnd = hWnd;
            ImmUnlockIMC(hSaveImc);
        }
        goto NotifySetActive;
    }

    /*
     * Non-NULL input context, window handle have to be updated.
     */
    if (pClientImc == NULL)
        return FALSE;

    pInputContext = ImmLockIMC(hImc);
    if (pInputContext == NULL) {
        ImmUnlockClientImc(pClientImc);
        return FALSE;
    }

    pInputContext->hWnd = hWnd;
    SetICF(pClientImc, IMCF_ACTIVE);

#ifdef LATER
    // Do uNumLangVKey checking later
#endif

    if (pInputContext->fdw31Compat & F31COMPAT_MCWHIDDEN)
        dwISC = ISC_SHOWUIALL - ISC_SHOWUICOMPOSITIONWINDOW;

    ImmUnlockIMC(hImc);

NotifySetActive:

    if (hImc != NULL_HIMC) {
        pImeDpi = ImmLockImeDpi(GetKeyboardLayout(0));
        if (pImeDpi != NULL) {
            (*pImeDpi->pfn.ImeSetActiveContext)(hImc, fActivate);
            ImmUnlockImeDpi(pImeDpi);
        }
    }

    /*
     * Notify UI
     */
    if (IsWindow(hWnd)) {
        SendMessage(hWnd, WM_IME_SETCONTEXT, fActivate, dwISC);
    }
    else if (!fActivate) {
        /*
         * Because hWnd is not there (maybe destroyed), we send 
         * WM_IME_SETCONTEXT to the default IME window.
         */
        if ((hDefImeWnd = ImmGetDefaultIMEWnd(NULL)) != NULL) {
            SendMessage(hDefImeWnd, WM_IME_SETCONTEXT, fActivate, dwISC);
        }
        else {
            RIPMSG0(RIP_WARNING,
                  "ImmSetActiveContext: can't send WM_IME_SETCONTEXT(FALSE).");
        }
    }
#ifdef DEBUG
    else {
        RIPMSG0(RIP_WARNING,
              "ImmSetActiveContext: can't send WM_IME_SETCONTEXT(TRUE).");
    }
#endif

#ifdef LATER
    // Implements ProcessIMCEvent() later.
#endif

    if (pClientImc != NULL)
        ImmUnlockClientImc(pClientImc);

    return TRUE;
}


/**************************************************************************\
* CreateInputContext
*
* 20-Feb-1996 wkwok       Created
\**************************************************************************/

BOOL CreateInputContext(
    HIMC hImc,
    HKL  hKL)
{
    PIMEDPI            pImeDpi;
    PCLIENTIMC         pClientImc;
    DWORD              dwPrivateDataSize;
    DWORD              fdwInitConvMode = 0;    // do it later
    BOOL               fInitOpen = FALSE;      // do it later
    PINPUTCONTEXT      pInputContext;
    PCOMPOSITIONSTRING pCompStr;
    PCANDIDATEINFO     pCandInfo;
    PGUIDELINE         pGuideLine;
    int                i;

    pInputContext = ImmLockIMC(hImc);
    if (!pInputContext) {
        RIPMSG1(RIP_WARNING, "CreateContext: Lock hIMC %x failure", hImc);
        goto CrIMCLockErrOut;
    }

    /*
     * Initialize the member of INPUTCONTEXT
     */
    pInputContext->hCompStr = ImmCreateIMCC(sizeof(COMPOSITIONSTRING));
    if (!pInputContext->hCompStr) {
        RIPMSG0(RIP_WARNING, "CreateContext: Create hCompStr failure");
        goto CrIMCUnlockIMC;
    }

    pCompStr = (PCOMPOSITIONSTRING)ImmLockIMCC(pInputContext->hCompStr);
    if (!pCompStr) {
        RIPMSG1(RIP_WARNING,
              "CreateContext: Lock hCompStr %x failure", pInputContext->hCompStr);
        goto CrIMCFreeCompStr;
    }

    pCompStr->dwSize = sizeof(COMPOSITIONSTRING);
    ImmUnlockIMCC(pInputContext->hCompStr);

    pInputContext->hCandInfo = ImmCreateIMCC(sizeof(CANDIDATEINFO));
    if (!pInputContext->hCandInfo) {
        RIPMSG0(RIP_WARNING, "CreateContext: Create hCandInfo failure");
        goto CrIMCFreeCompStr;
    }

    pCandInfo = (PCANDIDATEINFO)ImmLockIMCC(pInputContext->hCandInfo);
    if (!pCandInfo) {
        RIPMSG1(RIP_WARNING,
              "CreateContext: Lock hCandInfo %x failure", pInputContext->hCandInfo);
        goto CrIMCFreeCandInfo;
    }

    pCandInfo->dwSize = sizeof(CANDIDATEINFO);
    ImmUnlockIMCC(pInputContext->hCandInfo);

    pInputContext->hGuideLine = ImmCreateIMCC(sizeof(GUIDELINE));
    if (!pInputContext->hGuideLine) {
        RIPMSG0(RIP_WARNING, "CreateContext: Create hGuideLine failure");
        goto CrIMCFreeCandInfo;
    }

    pGuideLine = (PGUIDELINE)ImmLockIMCC(pInputContext->hGuideLine);
    if (!pGuideLine) {
        RIPMSG1(RIP_WARNING,
              "CreateContext: Lock hGuideLine %x failure", pInputContext->hGuideLine);
        goto CrIMCFreeGuideLine;
    }

    pGuideLine->dwSize = sizeof(GUIDELINE);
    ImmUnlockIMCC(pInputContext->hGuideLine);

    pInputContext->hMsgBuf = ImmCreateIMCC(sizeof(UINT));
    if (!pInputContext->hMsgBuf) {
        RIPMSG0(RIP_WARNING, "CreateContext: Create hMsgBuf failure");
        goto CrIMCFreeGuideLine;
    }

    pInputContext->dwNumMsgBuf = 0;
    pInputContext->fOpen = fInitOpen;
    pInputContext->fdwConversion = fdwInitConvMode;

    for (i = 0; i < 4; i++) {
        pInputContext->cfCandForm[i].dwIndex = (DWORD)(-1);
    }

    pImeDpi = ImmLockImeDpi(hKL);
    if (pImeDpi != NULL) {
        if ((pClientImc = ImmLockClientImc(hImc)) == NULL) {
            RIPMSG0(RIP_WARNING, "CreateContext: ImmLockClientImc() failure");
            ImmUnlockImeDpi(pImeDpi);
            goto CrIMCFreeMsgBuf;
        }

        /*
         * Unicode based IME expects an Uncode based input context.
         */
        if (pImeDpi->ImeInfo.fdwProperty & IME_PROP_UNICODE)
            SetICF(pClientImc, IMCF_UNICODE);

        ImmUnlockClientImc(pClientImc);

        dwPrivateDataSize = pImeDpi->ImeInfo.dwPrivateDataSize;
    }
    else {
        dwPrivateDataSize = sizeof(UINT);
    }

    pInputContext->hPrivate = ImmCreateIMCC(dwPrivateDataSize);
    if (!pInputContext->hPrivate) {
        RIPMSG0(RIP_WARNING, "CreateContext: Create hPrivate failure");
        ImmUnlockImeDpi(pImeDpi);
        goto CrIMCFreeMsgBuf;
    }

    if (pImeDpi != NULL) {
        (*pImeDpi->pfn.ImeSelect)(hImc, TRUE);
        ImmUnlockImeDpi(pImeDpi);
    }

    ImmUnlockIMC(hImc);
    return TRUE;

    /*
     * context failure case
     */
CrIMCFreeMsgBuf:
    ImmDestroyIMCC(pInputContext->hMsgBuf);
CrIMCFreeGuideLine:
    ImmDestroyIMCC(pInputContext->hGuideLine);
CrIMCFreeCandInfo:
    ImmDestroyIMCC(pInputContext->hCandInfo);
CrIMCFreeCompStr:
    ImmDestroyIMCC(pInputContext->hCompStr);
CrIMCUnlockIMC:
    ImmUnlockIMC(hImc);
CrIMCLockErrOut:
    return FALSE;
}


/**************************************************************************\
* DestroyInputContext
*
* 20-Feb-1996 wkwok       Created
\**************************************************************************/

BOOL DestroyInputContext(
    HIMC      hImc,
    HKL       hKL)
{
    PINPUTCONTEXT pInputContext;
    PIMEDPI       pImeDpi;
    PIMC          pImc;
    PCLIENTIMC    pClientImc;

    if (hImc == NULL_HIMC)
        return FALSE;

    pImc = HMValidateHandle((HANDLE)hImc, TYPE_INPUTCONTEXT);

    /*
     * Cannot destroy input context from other thread.
     */
    if (pImc == NULL || GETPTI(pImc) != PtiCurrent())
        return FALSE;

    /*
     * We are destroying this hImc and we don't bother
     * to lock its pClientImc.
     */
    pClientImc = (PCLIENTIMC)pImc->dwClientImcData;
    if (pClientImc == NULL) {
        /*
         * Client side Imc has not been initialzed yet.
         * We simply destroy this input context from kernel.
         */
        return NtUserDestroyInputContext(hImc);
    }

    if (TestICF(pClientImc, IMCF_DEFAULTIMC)) {
        /*
         * Cannot destroy default input context.
         */
        return FALSE;
    }

    if (TestICF(pClientImc, IMCF_INDESTROY)) {
        /*
         * This hImc is being destroyed. Returns as success.
         */
        return TRUE;
    }

    pInputContext = ImmLockIMC(hImc);
    if (!pInputContext) {
        RIPMSG1(RIP_WARNING, "DestroyContext: Lock hImc %x failure", hImc);
        return FALSE;
    }

    pImeDpi = ImmLockImeDpi(hKL);
    if (pImeDpi != NULL) {
        (*pImeDpi->pfn.ImeSelect)(hImc, FALSE);
        ImmUnlockImeDpi(pImeDpi);
    }

    SetICF(pClientImc, IMCF_INDESTROY);

    ImmDestroyIMCC(pInputContext->hPrivate);
    ImmDestroyIMCC(pInputContext->hMsgBuf);
    ImmDestroyIMCC(pInputContext->hGuideLine);
    ImmDestroyIMCC(pInputContext->hCandInfo);
    ImmDestroyIMCC(pInputContext->hCompStr);

    ImmUnlockIMC(hImc);

    return NtUserDestroyInputContext(hImc);
}


/**************************************************************************\
* SelectInputContext
*
* 20-Feb-1996 wkwok       Created
\**************************************************************************/

VOID SelectInputContext(
    HKL  hSelKL,
    HKL  hUnSelKL,
    HIMC hImc)
{
    PIMEDPI            pSelImeDpi, pUnSelImeDpi;
    PCLIENTIMC         pClientImc;
    PINPUTCONTEXT      pInputContext;
    DWORD              dwSelPriv = 0, dwUnSelPriv = 0, dwSize;
    HIMCC              hImcc;
    PCOMPOSITIONSTRING pCompStr;
    PCANDIDATEINFO     pCandInfo;
    PGUIDELINE         pGuideLine;
    BOOL               fLogFontInited;

    pClientImc = ImmLockClientImc(hImc);
    if (pClientImc == NULL)
        return;

    pSelImeDpi   = ImmLockImeDpi(hSelKL);
    pUnSelImeDpi = ImmLockImeDpi(hUnSelKL);

    /*
     * According to private memory size of the two layout, we decide
     * whether we nee to reallocate this memory block
     */
    if (pSelImeDpi != NULL)
        dwSelPriv = pSelImeDpi->ImeInfo.dwPrivateDataSize;

    if (pUnSelImeDpi != NULL)
        dwUnSelPriv = pUnSelImeDpi->ImeInfo.dwPrivateDataSize;

    dwSelPriv   = max(dwSelPriv,   sizeof(UINT));
    dwUnSelPriv = max(dwUnSelPriv, sizeof(UINT));

    /*
     * Unselect the input context.
     */
    if (pUnSelImeDpi != NULL)
        (*pUnSelImeDpi->pfn.ImeSelect)(hImc, FALSE);

    /*
     * Reinitialize the client side input context for the selected layout.
     */
    if ((pInputContext = ImmLockIMC(hImc)) != NULL) {

        fLogFontInited = ((pInputContext->fdwInit & INIT_LOGFONT) == INIT_LOGFONT);

        if (TestICF(pClientImc, IMCF_UNICODE) && pSelImeDpi != NULL &&
                !(pSelImeDpi->ImeInfo.fdwProperty & IME_PROP_UNICODE)) {
            /*
             * Check if there is any LOGFONT to be converted.
             */
            if (fLogFontInited) {
                LOGFONTA LogFontA;

                LFontWtoLFontA(&pInputContext->lfFont.W, &LogFontA);
                RtlCopyMemory(&pInputContext->lfFont.A, &LogFontA, sizeof(LOGFONTA));
            }

            ClrICF(pClientImc, IMCF_UNICODE);
        }
        else if (!TestICF(pClientImc, IMCF_UNICODE) && pSelImeDpi != NULL &&
                 (pSelImeDpi->ImeInfo.fdwProperty & IME_PROP_UNICODE)) {
            /*
             * Check if there is any LOGFONT to be converted.
             */
            if (fLogFontInited) {
                LOGFONTW LogFontW;

                LFontAtoLFontW(&pInputContext->lfFont.A, &LogFontW);
                RtlCopyMemory(&pInputContext->lfFont.W, &LogFontW, sizeof(LOGFONTW));
            }

            SetICF(pClientImc, IMCF_UNICODE);
        }

        /*
         * hPrivate
         */
        if (dwUnSelPriv != dwSelPriv) {
            hImcc = ImmReSizeIMCC(pInputContext->hPrivate, dwSelPriv);
            if (hImcc) {
                pInputContext->hPrivate = hImcc;
            }
            else {
                RIPMSG1(RIP_WARNING,
                      "SelectContext: resize hPrivate %lX failure",
                      pInputContext->hPrivate);
                ImmDestroyIMCC(pInputContext->hPrivate);
                pInputContext->hPrivate = ImmCreateIMCC(dwSelPriv);
            }
        }

        /*
         * hMsgBuf
         */
        dwSize = ImmGetIMCCSize(pInputContext->hMsgBuf);

        if (ImmGetIMCCLockCount(pInputContext->hMsgBuf) != 0 ||
                dwSize > IMCC_ALLOC_TOOLARGE) {

            RIPMSG0(RIP_WARNING, "SelectContext: create new hMsgBuf");
            ImmDestroyIMCC(pInputContext->hMsgBuf);
            pInputContext->hMsgBuf = ImmCreateIMCC(sizeof(UINT));
            pInputContext->dwNumMsgBuf = 0;
        }

        /*
         * hGuideLine
         */
        dwSize = ImmGetIMCCSize(pInputContext->hGuideLine);

        if (ImmGetIMCCLockCount(pInputContext->hGuideLine) != 0 ||
                dwSize < sizeof(GUIDELINE) || dwSize > IMCC_ALLOC_TOOLARGE) {

            RIPMSG0(RIP_WARNING, "SelectContext: create new hGuideLine");
            ImmDestroyIMCC(pInputContext->hGuideLine);
            pInputContext->hGuideLine = ImmCreateIMCC(sizeof(GUIDELINE));
            pGuideLine = (PGUIDELINE)ImmLockIMCC(pInputContext->hGuideLine);

            if (pGuideLine != NULL) {
                pGuideLine->dwSize = sizeof(GUIDELINE);
                ImmUnlockIMCC(pInputContext->hGuideLine);
            }
        }

        /*
         * hCandInfo
         */
        dwSize = ImmGetIMCCSize(pInputContext->hCandInfo);

        if (ImmGetIMCCLockCount(pInputContext->hCandInfo) != 0 ||
                dwSize < sizeof(CANDIDATEINFO) || dwSize > IMCC_ALLOC_TOOLARGE) {

            RIPMSG0(RIP_WARNING, "SelectContext: create new hCandInfo");
            ImmDestroyIMCC(pInputContext->hCandInfo);
            pInputContext->hCandInfo = ImmCreateIMCC(sizeof(CANDIDATEINFO));
            pCandInfo = (PCANDIDATEINFO)ImmLockIMCC(pInputContext->hCandInfo);

            if (pCandInfo != NULL) {
                pCandInfo->dwSize = sizeof(CANDIDATEINFO);
                ImmUnlockIMCC(pInputContext->hCandInfo);
            }
        }


        /*
         * hCompStr
         */
        dwSize = ImmGetIMCCSize(pInputContext->hCompStr);

        if (ImmGetIMCCLockCount(pInputContext->hCompStr) != 0 ||
                dwSize < sizeof(COMPOSITIONSTRING) || dwSize > IMCC_ALLOC_TOOLARGE) {

            RIPMSG0(RIP_WARNING, "SelectContext: create new hCompStr");
            ImmDestroyIMCC(pInputContext->hCompStr);
            pInputContext->hCompStr = ImmCreateIMCC(sizeof(COMPOSITIONSTRING));
            pCompStr = (PCOMPOSITIONSTRING)ImmLockIMCC(pInputContext->hCompStr);

            if (pCompStr != NULL) {
                pCompStr->dwSize = sizeof(COMPOSITIONSTRING);
                ImmUnlockIMCC(pInputContext->hCompStr);
            }
        }

        ImmUnlockIMC(hImc);
    }

    /*
     * Select the input context.
     */
    if (pSelImeDpi != NULL)
        (*pSelImeDpi->pfn.ImeSelect)(hImc, TRUE);

    ImmUnlockImeDpi(pUnSelImeDpi);
    ImmUnlockImeDpi(pSelImeDpi);
    ImmUnlockClientImc(pClientImc);

    return;
}


/**************************************************************************\
* EnumInputContext
*
* 20-Feb-1996 wkwok       Created
\**************************************************************************/

BOOL EnumInputContext(
    DWORD idThread,
    IMCENUMPROC lpfn,
    LONG lParam)
{
    UINT i;
    UINT cHimc;
    HIMC *phimcT;
    HIMC *phimcFirst;
    BOOL fSuccess = TRUE;

    /*
     * Get the himc list.  It is returned in a block of memory
     * allocated with ImmLocalAlloc.
     */
    if ((cHimc = BuildHimcList(idThread, &phimcFirst)) == 0) {
        return FALSE;
    }

    /*
     * Loop through the input contexts, call the function pointer back for
     * each one. End loop if either FALSE is returned or the end-of-list is
     * reached.
     */
    phimcT = phimcFirst;
    for (i = 0; i < cHimc; i++) {
        if (RevalidateHimc(*phimcT)) {
            if (!(fSuccess = (*lpfn)(*phimcT, lParam)))
                break;
        }
        phimcT++;
    }

    /*
     * Free up buffer and return status - TRUE if entire list was enumerated,
     * FALSE otherwise.
     */
    ImmLocalFree(phimcFirst);

    return fSuccess;
}


/**************************************************************************\
* BuildHimcList
*
* 20-Feb-1996 wkwok       Created
\**************************************************************************/

DWORD BuildHimcList(
    DWORD idThread,
    HIMC **pphimcFirst)
{
    UINT cHimc;
    HIMC *phimcFirst;
    NTSTATUS Status;
    int cTries;

    /*
     * Allocate a buffer to hold the names.
     */
    cHimc = 64;
    phimcFirst = ImmLocalAlloc(0, cHimc * sizeof(HIMC));
    if (phimcFirst == NULL)
        return 0;

    Status = NtUserBuildHimcList(idThread, cHimc, phimcFirst, &cHimc);

    /*
     * If the buffer wasn't big enough, reallocate
     * the buffer and try again.
     */
    cTries = 0;
    while (Status == STATUS_BUFFER_TOO_SMALL) {
        ImmLocalFree(phimcFirst);

        /*
         * If we can't seem to get it right,
         * call it quits
         */
        if (cTries++ == 10)
            return 0;

        phimcFirst = ImmLocalAlloc(0, cHimc * sizeof(HIMC));
        if (phimcFirst == NULL)
            return 0;

        Status = NtUserBuildHimcList(idThread, cHimc, phimcFirst, &cHimc);
    }

    if (!NT_SUCCESS(Status) || cHimc == 0) {
        ImmLocalFree(phimcFirst);
        return 0;
    }

    *pphimcFirst = phimcFirst;

    return cHimc;
}